nucleus-mcp 1.0.7 → 1.2.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.
- package/package.json +2 -4
- package/.github/workflows/ci.yml +0 -72
- package/.smithery/shttp/module.js +0 -3
- package/.smithery/shttp/module.js.map +0 -7
- package/.smithery/stdio/index.cjs +0 -74
- package/.smithery/stdio/index.cjs.map +0 -7
- package/CHANGELOG.md +0 -65
- package/CODE_OF_CONDUCT.md +0 -34
- package/COMPARISON.md +0 -20
- package/CONTRIBUTING.md +0 -137
- package/DEVELOPMENT.md +0 -109
- package/LICENSE +0 -21
- package/README.md +0 -319
- package/ROADMAP.md +0 -24
- package/SECURITY.md +0 -88
- package/docs/COMMUNITY_FAQ.md +0 -48
- package/docs/COMPETITIVE_ANALYSIS.md +0 -90
- package/docs/DEMO_SCRIPT.md +0 -86
- package/docs/ENTERPRISE.md +0 -169
- package/docs/FAQ.md +0 -228
- package/docs/QUICK_START.md +0 -155
- package/docs/README.md +0 -67
- package/docs/RECURSIVE_SNAP_DEMO.gif +0 -0
- package/docs/social-preview-hq.png +0 -0
- package/docs/social-preview.png +0 -0
- package/examples/basic_usage.py +0 -84
- package/package.json.real +0 -24
- package/pyproject.toml +0 -50
- package/registry-server.json +0 -20
- package/scripts/demo_governance.py +0 -109
- package/scripts/sse_bridge.py +0 -87
- package/scripts/verify_windows_paths.py +0 -48
- package/server.json +0 -20
- package/src/mcp_server_nucleus/__init__.py +0 -8583
- package/src/mcp_server_nucleus/__main__.py +0 -9
- package/src/mcp_server_nucleus/cli.py +0 -1225
- package/src/mcp_server_nucleus/commitment_core.py +0 -146
- package/src/mcp_server_nucleus/commitment_ledger.py +0 -841
- package/src/mcp_server_nucleus/core/__init__.py +0 -6
- package/src/mcp_server_nucleus/core/orchestrator.py +0 -9
- package/src/mcp_server_nucleus/core/tool_registration_impl.py +0 -99
- package/src/mcp_server_nucleus/daemon/safety/lock.py +0 -121
- package/src/mcp_server_nucleus/deployment/render_integration.py +0 -4
- package/src/mcp_server_nucleus/hypervisor/__init__.py +0 -0
- package/src/mcp_server_nucleus/hypervisor/injector.py +0 -83
- package/src/mcp_server_nucleus/hypervisor/locker.py +0 -130
- package/src/mcp_server_nucleus/hypervisor/watchdog.py +0 -71
- package/src/mcp_server_nucleus/runtime/__init__.py +0 -58
- package/src/mcp_server_nucleus/runtime/agent.py +0 -517
- package/src/mcp_server_nucleus/runtime/agent_pool.py +0 -797
- package/src/mcp_server_nucleus/runtime/agents/base.py +0 -34
- package/src/mcp_server_nucleus/runtime/agents/oracle/manifest.json +0 -28
- package/src/mcp_server_nucleus/runtime/auth.py +0 -98
- package/src/mcp_server_nucleus/runtime/autopilot.py +0 -1057
- package/src/mcp_server_nucleus/runtime/broker.py +0 -126
- package/src/mcp_server_nucleus/runtime/budget.py +0 -153
- package/src/mcp_server_nucleus/runtime/capabilities/base.py +0 -20
- package/src/mcp_server_nucleus/runtime/capabilities/brain_ops.py +0 -253
- package/src/mcp_server_nucleus/runtime/capabilities/code_ops.py +0 -192
- package/src/mcp_server_nucleus/runtime/capabilities/depth_tracker.py +0 -81
- package/src/mcp_server_nucleus/runtime/capabilities/feature_map.py +0 -274
- package/src/mcp_server_nucleus/runtime/capabilities/marketing_engine.py +0 -205
- package/src/mcp_server_nucleus/runtime/capabilities/memory_ops.py +0 -79
- package/src/mcp_server_nucleus/runtime/capabilities/proof_system.py +0 -127
- package/src/mcp_server_nucleus/runtime/capabilities/render_ops.py +0 -80
- package/src/mcp_server_nucleus/runtime/capabilities/render_poller_cap.py +0 -266
- package/src/mcp_server_nucleus/runtime/capabilities/self_healing.py +0 -110
- package/src/mcp_server_nucleus/runtime/capabilities/strategy.py +0 -138
- package/src/mcp_server_nucleus/runtime/capabilities/synthesizer.py +0 -134
- package/src/mcp_server_nucleus/runtime/capabilities/web_ops.py +0 -94
- package/src/mcp_server_nucleus/runtime/common.py +0 -134
- package/src/mcp_server_nucleus/runtime/context_manager.py +0 -342
- package/src/mcp_server_nucleus/runtime/context_rules.json +0 -86
- package/src/mcp_server_nucleus/runtime/crdt_task_store.py +0 -349
- package/src/mcp_server_nucleus/runtime/critic.py +0 -150
- package/src/mcp_server_nucleus/runtime/daemon.py +0 -142
- package/src/mcp_server_nucleus/runtime/dashboard.py +0 -1087
- package/src/mcp_server_nucleus/runtime/depth_ops.py +0 -339
- package/src/mcp_server_nucleus/runtime/event_ops.py +0 -103
- package/src/mcp_server_nucleus/runtime/event_stream.py +0 -213
- package/src/mcp_server_nucleus/runtime/export.py +0 -64
- package/src/mcp_server_nucleus/runtime/factory.py +0 -594
- package/src/mcp_server_nucleus/runtime/federation.py +0 -1040
- package/src/mcp_server_nucleus/runtime/fetcher.py +0 -92
- package/src/mcp_server_nucleus/runtime/file_monitor.py +0 -208
- package/src/mcp_server_nucleus/runtime/firestore_bridge.py +0 -91
- package/src/mcp_server_nucleus/runtime/gcloud_ops.py +0 -186
- package/src/mcp_server_nucleus/runtime/hooks.py +0 -246
- package/src/mcp_server_nucleus/runtime/identity/gatekeeper.py +0 -106
- package/src/mcp_server_nucleus/runtime/identity/keygen.py +0 -147
- package/src/mcp_server_nucleus/runtime/identity/manifest.py +0 -62
- package/src/mcp_server_nucleus/runtime/identity/trust.py +0 -27
- package/src/mcp_server_nucleus/runtime/inspector.py +0 -74
- package/src/mcp_server_nucleus/runtime/installer.py +0 -81
- package/src/mcp_server_nucleus/runtime/ipc_auth.py +0 -377
- package/src/mcp_server_nucleus/runtime/lifecycle.py +0 -129
- package/src/mcp_server_nucleus/runtime/llm_client.py +0 -408
- package/src/mcp_server_nucleus/runtime/llm_tiers.json +0 -88
- package/src/mcp_server_nucleus/runtime/locking.py +0 -228
- package/src/mcp_server_nucleus/runtime/loops/fixer.py +0 -110
- package/src/mcp_server_nucleus/runtime/memory.py +0 -158
- package/src/mcp_server_nucleus/runtime/mounter.py +0 -210
- package/src/mcp_server_nucleus/runtime/mounter_ops.py +0 -250
- package/src/mcp_server_nucleus/runtime/nuke_protocol.py +0 -147
- package/src/mcp_server_nucleus/runtime/orchestrator.py +0 -370
- package/src/mcp_server_nucleus/runtime/orchestrator_unified.py +0 -263
- package/src/mcp_server_nucleus/runtime/orchestrator_v3.py +0 -617
- package/src/mcp_server_nucleus/runtime/plugin_loader.py +0 -84
- package/src/mcp_server_nucleus/runtime/policy.py +0 -117
- package/src/mcp_server_nucleus/runtime/profiling.py +0 -219
- package/src/mcp_server_nucleus/runtime/prometheus.py +0 -286
- package/src/mcp_server_nucleus/runtime/proposals.py +0 -101
- package/src/mcp_server_nucleus/runtime/publisher.py +0 -109
- package/src/mcp_server_nucleus/runtime/pulse.py +0 -516
- package/src/mcp_server_nucleus/runtime/registry.py +0 -82
- package/src/mcp_server_nucleus/runtime/render_ops.py +0 -83
- package/src/mcp_server_nucleus/runtime/schema_gen.py +0 -140
- package/src/mcp_server_nucleus/runtime/session_ops.py +0 -224
- package/src/mcp_server_nucleus/runtime/stdio_server.py +0 -633
- package/src/mcp_server_nucleus/runtime/storage.py +0 -83
- package/src/mcp_server_nucleus/runtime/strategy.py +0 -91
- package/src/mcp_server_nucleus/runtime/swarm.py +0 -170
- package/src/mcp_server_nucleus/runtime/sync_ops.py +0 -878
- package/src/mcp_server_nucleus/runtime/task_ingestion.py +0 -1020
- package/src/mcp_server_nucleus/runtime/task_ops.py +0 -449
- package/src/mcp_server_nucleus/runtime/task_scheduler.py +0 -488
- package/src/mcp_server_nucleus/runtime/team.py +0 -59
- package/src/mcp_server_nucleus/runtime/triggers.py +0 -237
- package/src/mcp_server_nucleus/runtime/vector_store.py +0 -125
- package/src/mcp_server_nucleus/runtime/watcher.py +0 -112
- package/src/mcp_server_nucleus/tool_tiers.py +0 -243
- package/tests/__init__.py +0 -1
- package/tests/conftest.py +0 -42
- package/tests/test_core.py +0 -235
- package/tests/test_search.py +0 -69
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
from typing import List, Dict, Any
|
|
2
|
-
from .base import Capability
|
|
3
|
-
|
|
4
|
-
class WebOps(Capability):
|
|
5
|
-
@property
|
|
6
|
-
def name(self) -> str:
|
|
7
|
-
return "web_ops"
|
|
8
|
-
|
|
9
|
-
@property
|
|
10
|
-
def description(self) -> str:
|
|
11
|
-
return "Tools for browsing the web (simulated via requests/search)."
|
|
12
|
-
|
|
13
|
-
def get_tools(self) -> List[Dict[str, Any]]:
|
|
14
|
-
return [
|
|
15
|
-
{
|
|
16
|
-
"name": "web_search",
|
|
17
|
-
"description": "Search Google for a query.",
|
|
18
|
-
"parameters": {
|
|
19
|
-
"type": "object",
|
|
20
|
-
"properties": {
|
|
21
|
-
"query": {"type": "string"},
|
|
22
|
-
"num_results": {"type": "integer", "default": 5}
|
|
23
|
-
},
|
|
24
|
-
"required": ["query"]
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
"name": "web_read_page",
|
|
29
|
-
"description": "Read text content from a URL.",
|
|
30
|
-
"parameters": {
|
|
31
|
-
"type": "object",
|
|
32
|
-
"properties": {
|
|
33
|
-
"url": {"type": "string"}
|
|
34
|
-
},
|
|
35
|
-
"required": ["url"]
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
]
|
|
39
|
-
|
|
40
|
-
def execute_tool(self, tool_name: str, args: Dict) -> str:
|
|
41
|
-
"""Execute web operations."""
|
|
42
|
-
if tool_name == "web_search":
|
|
43
|
-
query = args['query']
|
|
44
|
-
num_results = args.get('num_results', 5)
|
|
45
|
-
try:
|
|
46
|
-
from duckduckgo_search import DDGS
|
|
47
|
-
with DDGS() as ddgs:
|
|
48
|
-
results = list(ddgs.text(query, max_results=num_results))
|
|
49
|
-
|
|
50
|
-
if not results:
|
|
51
|
-
return f"No results found for '{query}'."
|
|
52
|
-
|
|
53
|
-
formatted = []
|
|
54
|
-
for r in results:
|
|
55
|
-
formatted.append(f"- [{r['title']}]({r['href']})\n {r['body']}")
|
|
56
|
-
|
|
57
|
-
return "\n\n".join(formatted)
|
|
58
|
-
except ImportError:
|
|
59
|
-
return "Error: duckduckgo-search not installed. Please install it to use this feature."
|
|
60
|
-
except Exception as e:
|
|
61
|
-
return f"Search error: {e}"
|
|
62
|
-
|
|
63
|
-
elif tool_name == "web_read_page":
|
|
64
|
-
url = args['url']
|
|
65
|
-
try:
|
|
66
|
-
import urllib.request
|
|
67
|
-
from bs4 import BeautifulSoup
|
|
68
|
-
|
|
69
|
-
headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'}
|
|
70
|
-
req = urllib.request.Request(url, headers=headers)
|
|
71
|
-
|
|
72
|
-
with urllib.request.urlopen(req, timeout=10) as response:
|
|
73
|
-
html_content = response.read().decode('utf-8')
|
|
74
|
-
|
|
75
|
-
soup = BeautifulSoup(html_content, 'html.parser')
|
|
76
|
-
|
|
77
|
-
# Remove script and style elements
|
|
78
|
-
for script in soup(['script', 'style', 'nav', 'footer', 'header']):
|
|
79
|
-
script.decompose()
|
|
80
|
-
|
|
81
|
-
text = soup.get_text()
|
|
82
|
-
|
|
83
|
-
# Clean up whitespace
|
|
84
|
-
lines = (line.strip() for line in text.splitlines())
|
|
85
|
-
chunks = (phrase.strip() for line in lines for phrase in line.split(" "))
|
|
86
|
-
text = '\n'.join(chunk for chunk in chunks if chunk)
|
|
87
|
-
|
|
88
|
-
return f"URL: {url}\n\n{text[:8000]}..." # Truncate to 8k chars
|
|
89
|
-
except ImportError:
|
|
90
|
-
return "Error: beautifulsoup4 not installed. Please install it to use this feature."
|
|
91
|
-
except Exception as e:
|
|
92
|
-
return f"Error reading {url}: {e}"
|
|
93
|
-
|
|
94
|
-
return f"Tool {tool_name} not found in WebOps."
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Nucleus Runtime - Common Utilities
|
|
3
|
-
==================================
|
|
4
|
-
Shared utilities and constants for the Nucleus runtime.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import os
|
|
8
|
-
import json
|
|
9
|
-
import logging
|
|
10
|
-
import sys
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
from datetime import datetime, timezone
|
|
13
|
-
from typing import Dict, Any, Optional
|
|
14
|
-
|
|
15
|
-
# ============================================================
|
|
16
|
-
# STRUCTURED LOGGING SYSTEM (AG-010)
|
|
17
|
-
# ============================================================
|
|
18
|
-
|
|
19
|
-
class JSONFormatter(logging.Formatter):
|
|
20
|
-
"""Formats log records as JSON objects for machine readability."""
|
|
21
|
-
def format(self, record):
|
|
22
|
-
log_entry = {
|
|
23
|
-
"timestamp": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
|
|
24
|
-
"level": record.levelname,
|
|
25
|
-
"logger": record.name,
|
|
26
|
-
"message": record.getMessage(),
|
|
27
|
-
"module": record.module,
|
|
28
|
-
"line": record.lineno
|
|
29
|
-
}
|
|
30
|
-
if record.exc_info:
|
|
31
|
-
log_entry["exception"] = self.formatException(record.exc_info)
|
|
32
|
-
return json.dumps(log_entry)
|
|
33
|
-
|
|
34
|
-
def setup_nucleus_logging(name: str = "nucleus", level: int = logging.INFO):
|
|
35
|
-
"""Setup structured logging for a component."""
|
|
36
|
-
logger = logging.getLogger(name)
|
|
37
|
-
logger.setLevel(level)
|
|
38
|
-
|
|
39
|
-
# Avoid duplicate handlers
|
|
40
|
-
if not logger.handlers:
|
|
41
|
-
handler = logging.StreamHandler(sys.stdout)
|
|
42
|
-
|
|
43
|
-
# Use JSON if specified via env
|
|
44
|
-
if os.environ.get("NUCLEUS_LOG_JSON", "false").lower() == "true":
|
|
45
|
-
handler.setFormatter(JSONFormatter())
|
|
46
|
-
else:
|
|
47
|
-
handler.setFormatter(logging.Formatter(
|
|
48
|
-
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
49
|
-
))
|
|
50
|
-
|
|
51
|
-
logger.addHandler(handler)
|
|
52
|
-
return logger
|
|
53
|
-
|
|
54
|
-
# Common logger
|
|
55
|
-
logger = setup_nucleus_logging()
|
|
56
|
-
|
|
57
|
-
def get_brain_path() -> Path:
|
|
58
|
-
"""Get the brain path from environment variable (read dynamically for testing)."""
|
|
59
|
-
brain_path = os.environ.get("NUCLEAR_BRAIN_PATH")
|
|
60
|
-
if not brain_path:
|
|
61
|
-
raise ValueError("NUCLEAR_BRAIN_PATH environment variable not set")
|
|
62
|
-
path = Path(brain_path)
|
|
63
|
-
if not path.exists():
|
|
64
|
-
raise ValueError(f"Brain path does not exist: {brain_path}")
|
|
65
|
-
return path
|
|
66
|
-
|
|
67
|
-
def make_response(success: bool, data=None, error=None, error_code=None):
|
|
68
|
-
"""Standardized API response formatter.
|
|
69
|
-
|
|
70
|
-
Args:
|
|
71
|
-
success: Whether the operation succeeded
|
|
72
|
-
data: Successful payload (dict, list, string)
|
|
73
|
-
error: Error message if failed
|
|
74
|
-
error_code: Optional short code for error (e.g. ERR_NOT_FOUND)
|
|
75
|
-
|
|
76
|
-
Returns:
|
|
77
|
-
JSON string matching Nucleus Standard Response Schema
|
|
78
|
-
"""
|
|
79
|
-
return json.dumps({
|
|
80
|
-
"success": success,
|
|
81
|
-
"data": data,
|
|
82
|
-
"error": error,
|
|
83
|
-
"error_code": error_code,
|
|
84
|
-
"timestamp": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
|
|
85
|
-
}, indent=2)
|
|
86
|
-
|
|
87
|
-
def _get_state(path: Optional[str] = None) -> Dict:
|
|
88
|
-
"""Core logic for getting state."""
|
|
89
|
-
try:
|
|
90
|
-
brain = get_brain_path()
|
|
91
|
-
state_path = brain / "ledger" / "state.json"
|
|
92
|
-
|
|
93
|
-
if not state_path.exists():
|
|
94
|
-
return {}
|
|
95
|
-
|
|
96
|
-
from .sync_ops import sync_lock
|
|
97
|
-
with sync_lock(brain, timeout=2):
|
|
98
|
-
with open(state_path, "r") as f:
|
|
99
|
-
state = json.load(f)
|
|
100
|
-
|
|
101
|
-
if path:
|
|
102
|
-
keys = path.split('.')
|
|
103
|
-
val = state
|
|
104
|
-
for k in keys:
|
|
105
|
-
val = val.get(k, {})
|
|
106
|
-
return val
|
|
107
|
-
|
|
108
|
-
return state
|
|
109
|
-
except Exception as e:
|
|
110
|
-
logger.error(f"Error reading state: {e}")
|
|
111
|
-
return {}
|
|
112
|
-
|
|
113
|
-
def _update_state(updates: Dict[str, Any]) -> str:
|
|
114
|
-
"""Core logic for updating state."""
|
|
115
|
-
try:
|
|
116
|
-
brain = get_brain_path()
|
|
117
|
-
state_path = brain / "ledger" / "state.json"
|
|
118
|
-
state_path.parent.mkdir(parents=True, exist_ok=True)
|
|
119
|
-
|
|
120
|
-
from .sync_ops import sync_lock
|
|
121
|
-
with sync_lock(brain, timeout=5):
|
|
122
|
-
current_state = {}
|
|
123
|
-
if state_path.exists():
|
|
124
|
-
with open(state_path, "r") as f:
|
|
125
|
-
current_state = json.load(f)
|
|
126
|
-
|
|
127
|
-
current_state.update(updates)
|
|
128
|
-
|
|
129
|
-
with open(state_path, "w") as f:
|
|
130
|
-
json.dump(current_state, f, indent=2)
|
|
131
|
-
|
|
132
|
-
return "State updated successfully"
|
|
133
|
-
except Exception as e:
|
|
134
|
-
return f"Error updating state: {str(e)}"
|
|
@@ -1,342 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
v0.6.0 DSoR: Context Manager
|
|
3
|
-
|
|
4
|
-
Provides stateless hashing of the current world-state for decision provenance.
|
|
5
|
-
This module enables verification of state before/after agent turns to ensure
|
|
6
|
-
integrity and detect unauthorized mutations.
|
|
7
|
-
|
|
8
|
-
Part of the Decision System of Record (DSoR) initiative.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
import hashlib
|
|
12
|
-
import json
|
|
13
|
-
import os
|
|
14
|
-
from datetime import datetime, timezone
|
|
15
|
-
from pathlib import Path
|
|
16
|
-
from typing import Dict, Any, List, Optional, Tuple
|
|
17
|
-
from dataclasses import dataclass, field, asdict
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def get_brain_path() -> Path:
|
|
21
|
-
"""Get the brain path from environment."""
|
|
22
|
-
return Path(os.getenv("NUCLEAR_BRAIN_PATH", "/Users/lokeshgarg/ai-mvp-backend/.brain"))
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@dataclass
|
|
26
|
-
class ContextSnapshot:
|
|
27
|
-
"""
|
|
28
|
-
Immutable snapshot of world-state at a point in time.
|
|
29
|
-
Used for before/after comparisons in agent turns.
|
|
30
|
-
"""
|
|
31
|
-
snapshot_id: str
|
|
32
|
-
timestamp: str
|
|
33
|
-
state_hash: str
|
|
34
|
-
component_hashes: Dict[str, str] = field(default_factory=dict)
|
|
35
|
-
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
36
|
-
|
|
37
|
-
def to_dict(self) -> Dict[str, Any]:
|
|
38
|
-
return asdict(self)
|
|
39
|
-
|
|
40
|
-
@classmethod
|
|
41
|
-
def from_dict(cls, data: Dict[str, Any]) -> "ContextSnapshot":
|
|
42
|
-
return cls(**data)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
@dataclass
|
|
46
|
-
class StateVerificationResult:
|
|
47
|
-
"""Result of comparing two context snapshots."""
|
|
48
|
-
is_valid: bool
|
|
49
|
-
before_hash: str
|
|
50
|
-
after_hash: str
|
|
51
|
-
mutations_detected: List[str] = field(default_factory=list)
|
|
52
|
-
drift_components: Dict[str, Tuple[str, str]] = field(default_factory=dict)
|
|
53
|
-
verification_timestamp: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
class ContextManager:
|
|
57
|
-
"""
|
|
58
|
-
v0.6.0 DSoR: Manages context state hashing and verification.
|
|
59
|
-
|
|
60
|
-
Provides:
|
|
61
|
-
1. Stateless hashing of current world-state
|
|
62
|
-
2. Before/after verification for agent turns
|
|
63
|
-
3. Drift detection for unauthorized mutations
|
|
64
|
-
"""
|
|
65
|
-
|
|
66
|
-
def __init__(self, brain_path: Optional[Path] = None):
|
|
67
|
-
self.brain_path = brain_path or get_brain_path()
|
|
68
|
-
self._snapshot_counter = 0
|
|
69
|
-
|
|
70
|
-
def _hash_file(self, file_path: Path) -> Optional[str]:
|
|
71
|
-
"""Compute SHA-256 hash of a file's contents."""
|
|
72
|
-
try:
|
|
73
|
-
if file_path.exists() and file_path.is_file():
|
|
74
|
-
content = file_path.read_bytes()
|
|
75
|
-
return hashlib.sha256(content).hexdigest()[:16]
|
|
76
|
-
except Exception:
|
|
77
|
-
pass
|
|
78
|
-
return None
|
|
79
|
-
|
|
80
|
-
def _hash_dict(self, data: Dict[str, Any]) -> str:
|
|
81
|
-
"""Compute deterministic hash of a dictionary."""
|
|
82
|
-
serialized = json.dumps(data, sort_keys=True, default=str)
|
|
83
|
-
return hashlib.sha256(serialized.encode()).hexdigest()[:16]
|
|
84
|
-
|
|
85
|
-
def _collect_ledger_state(self) -> Dict[str, Any]:
|
|
86
|
-
"""Collect state from the ledger directory."""
|
|
87
|
-
ledger_path = self.brain_path / "ledger"
|
|
88
|
-
state = {
|
|
89
|
-
"engrams_hash": None,
|
|
90
|
-
"events_hash": None,
|
|
91
|
-
"tasks_hash": None,
|
|
92
|
-
"decisions_hash": None,
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if ledger_path.exists():
|
|
96
|
-
# Hash key ledger files
|
|
97
|
-
engrams_file = ledger_path / "engrams.jsonl"
|
|
98
|
-
events_file = ledger_path / "events.jsonl"
|
|
99
|
-
tasks_file = ledger_path / "tasks.json"
|
|
100
|
-
decisions_dir = ledger_path / "decisions"
|
|
101
|
-
|
|
102
|
-
state["engrams_hash"] = self._hash_file(engrams_file)
|
|
103
|
-
state["events_hash"] = self._hash_file(events_file)
|
|
104
|
-
state["tasks_hash"] = self._hash_file(tasks_file)
|
|
105
|
-
|
|
106
|
-
if decisions_dir.exists():
|
|
107
|
-
decisions_file = decisions_dir / "decisions.jsonl"
|
|
108
|
-
state["decisions_hash"] = self._hash_file(decisions_file)
|
|
109
|
-
|
|
110
|
-
return state
|
|
111
|
-
|
|
112
|
-
def _collect_slots_state(self) -> Dict[str, Any]:
|
|
113
|
-
"""Collect state from the slots directory."""
|
|
114
|
-
slots_path = self.brain_path / "slots"
|
|
115
|
-
state = {"registry_hash": None, "slot_count": 0}
|
|
116
|
-
|
|
117
|
-
if slots_path.exists():
|
|
118
|
-
registry_file = slots_path / "registry.json"
|
|
119
|
-
state["registry_hash"] = self._hash_file(registry_file)
|
|
120
|
-
state["slot_count"] = len(list(slots_path.glob("slot_*.json")))
|
|
121
|
-
|
|
122
|
-
return state
|
|
123
|
-
|
|
124
|
-
def _collect_mounts_state(self) -> Dict[str, Any]:
|
|
125
|
-
"""Collect state from mounted servers."""
|
|
126
|
-
mounts_file = self.brain_path / "ledger" / "mounts.json"
|
|
127
|
-
state = {"mounts_hash": None, "mount_count": 0}
|
|
128
|
-
|
|
129
|
-
if mounts_file.exists():
|
|
130
|
-
state["mounts_hash"] = self._hash_file(mounts_file)
|
|
131
|
-
try:
|
|
132
|
-
with open(mounts_file) as f:
|
|
133
|
-
mounts = json.load(f)
|
|
134
|
-
state["mount_count"] = len(mounts.get("servers", []))
|
|
135
|
-
except Exception:
|
|
136
|
-
pass
|
|
137
|
-
|
|
138
|
-
return state
|
|
139
|
-
|
|
140
|
-
def compute_world_state_hash(self, include_docs: bool = False,
|
|
141
|
-
recent_events: Optional[List[str]] = None) -> Tuple[str, Dict[str, str]]:
|
|
142
|
-
"""
|
|
143
|
-
Compute a comprehensive hash of the current world-state.
|
|
144
|
-
|
|
145
|
-
Args:
|
|
146
|
-
include_docs: Whether to include document checksums
|
|
147
|
-
recent_events: Optional list of recent event strings to include
|
|
148
|
-
|
|
149
|
-
Returns:
|
|
150
|
-
Tuple of (overall_hash, component_hashes)
|
|
151
|
-
"""
|
|
152
|
-
components = {}
|
|
153
|
-
|
|
154
|
-
# 1. Ledger state
|
|
155
|
-
ledger_state = self._collect_ledger_state()
|
|
156
|
-
components["ledger"] = self._hash_dict(ledger_state)
|
|
157
|
-
|
|
158
|
-
# 2. Slots state
|
|
159
|
-
slots_state = self._collect_slots_state()
|
|
160
|
-
components["slots"] = self._hash_dict(slots_state)
|
|
161
|
-
|
|
162
|
-
# 3. Mounts state
|
|
163
|
-
mounts_state = self._collect_mounts_state()
|
|
164
|
-
components["mounts"] = self._hash_dict(mounts_state)
|
|
165
|
-
|
|
166
|
-
# 4. Recent events (if provided)
|
|
167
|
-
if recent_events:
|
|
168
|
-
events_blob = json.dumps(recent_events[-10:], sort_keys=True)
|
|
169
|
-
components["recent_events"] = hashlib.sha256(events_blob.encode()).hexdigest()[:16]
|
|
170
|
-
|
|
171
|
-
# 5. Optional: Include docs checksums
|
|
172
|
-
if include_docs:
|
|
173
|
-
docs_path = self.brain_path / "artifacts"
|
|
174
|
-
if docs_path.exists():
|
|
175
|
-
doc_hashes = []
|
|
176
|
-
for md_file in docs_path.rglob("*.md"):
|
|
177
|
-
h = self._hash_file(md_file)
|
|
178
|
-
if h:
|
|
179
|
-
doc_hashes.append(f"{md_file.name}:{h}")
|
|
180
|
-
if doc_hashes:
|
|
181
|
-
components["docs"] = hashlib.sha256(
|
|
182
|
-
"|".join(sorted(doc_hashes)).encode()
|
|
183
|
-
).hexdigest()[:16]
|
|
184
|
-
|
|
185
|
-
# Compute overall hash from components
|
|
186
|
-
overall = self._hash_dict(components)
|
|
187
|
-
|
|
188
|
-
return overall, components
|
|
189
|
-
|
|
190
|
-
def take_snapshot(self, include_docs: bool = False,
|
|
191
|
-
recent_events: Optional[List[str]] = None,
|
|
192
|
-
metadata: Optional[Dict[str, Any]] = None) -> ContextSnapshot:
|
|
193
|
-
"""
|
|
194
|
-
Take an immutable snapshot of the current world-state.
|
|
195
|
-
|
|
196
|
-
Args:
|
|
197
|
-
include_docs: Whether to include document checksums
|
|
198
|
-
recent_events: Optional list of recent event strings
|
|
199
|
-
metadata: Optional metadata to attach to snapshot
|
|
200
|
-
|
|
201
|
-
Returns:
|
|
202
|
-
ContextSnapshot instance
|
|
203
|
-
"""
|
|
204
|
-
self._snapshot_counter += 1
|
|
205
|
-
state_hash, component_hashes = self.compute_world_state_hash(
|
|
206
|
-
include_docs=include_docs,
|
|
207
|
-
recent_events=recent_events
|
|
208
|
-
)
|
|
209
|
-
|
|
210
|
-
snapshot = ContextSnapshot(
|
|
211
|
-
snapshot_id=f"snap-{self._snapshot_counter:06d}",
|
|
212
|
-
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
213
|
-
state_hash=state_hash,
|
|
214
|
-
component_hashes=component_hashes,
|
|
215
|
-
metadata=metadata or {}
|
|
216
|
-
)
|
|
217
|
-
|
|
218
|
-
return snapshot
|
|
219
|
-
|
|
220
|
-
def verify_state_integrity(self, before: ContextSnapshot,
|
|
221
|
-
after: ContextSnapshot,
|
|
222
|
-
allowed_mutations: Optional[List[str]] = None) -> StateVerificationResult:
|
|
223
|
-
"""
|
|
224
|
-
Verify state integrity by comparing before/after snapshots.
|
|
225
|
-
|
|
226
|
-
Args:
|
|
227
|
-
before: Snapshot taken before agent turn
|
|
228
|
-
after: Snapshot taken after agent turn
|
|
229
|
-
allowed_mutations: List of component names that are allowed to change
|
|
230
|
-
|
|
231
|
-
Returns:
|
|
232
|
-
StateVerificationResult with drift analysis
|
|
233
|
-
"""
|
|
234
|
-
allowed = set(allowed_mutations or [])
|
|
235
|
-
mutations = []
|
|
236
|
-
drift_components = {}
|
|
237
|
-
|
|
238
|
-
# Compare component hashes
|
|
239
|
-
all_components = set(before.component_hashes.keys()) | set(after.component_hashes.keys())
|
|
240
|
-
|
|
241
|
-
for component in all_components:
|
|
242
|
-
before_hash = before.component_hashes.get(component)
|
|
243
|
-
after_hash = after.component_hashes.get(component)
|
|
244
|
-
|
|
245
|
-
if before_hash != after_hash:
|
|
246
|
-
mutations.append(component)
|
|
247
|
-
drift_components[component] = (before_hash or "none", after_hash or "none")
|
|
248
|
-
|
|
249
|
-
# Check if mutations are within allowed set
|
|
250
|
-
unauthorized_mutations = [m for m in mutations if m not in allowed]
|
|
251
|
-
is_valid = len(unauthorized_mutations) == 0
|
|
252
|
-
|
|
253
|
-
return StateVerificationResult(
|
|
254
|
-
is_valid=is_valid,
|
|
255
|
-
before_hash=before.state_hash,
|
|
256
|
-
after_hash=after.state_hash,
|
|
257
|
-
mutations_detected=mutations,
|
|
258
|
-
drift_components=drift_components
|
|
259
|
-
)
|
|
260
|
-
|
|
261
|
-
def persist_snapshot(self, snapshot: ContextSnapshot) -> Path:
|
|
262
|
-
"""
|
|
263
|
-
Persist a snapshot to the ledger for audit purposes.
|
|
264
|
-
|
|
265
|
-
Args:
|
|
266
|
-
snapshot: The snapshot to persist
|
|
267
|
-
|
|
268
|
-
Returns:
|
|
269
|
-
Path to the persisted snapshot file
|
|
270
|
-
"""
|
|
271
|
-
snapshots_dir = self.brain_path / "ledger" / "snapshots"
|
|
272
|
-
snapshots_dir.mkdir(parents=True, exist_ok=True)
|
|
273
|
-
|
|
274
|
-
snapshot_file = snapshots_dir / f"{snapshot.snapshot_id}.json"
|
|
275
|
-
snapshot_file.write_text(json.dumps(snapshot.to_dict(), indent=2))
|
|
276
|
-
|
|
277
|
-
return snapshot_file
|
|
278
|
-
|
|
279
|
-
def load_snapshot(self, snapshot_id: str) -> Optional[ContextSnapshot]:
|
|
280
|
-
"""
|
|
281
|
-
Load a previously persisted snapshot.
|
|
282
|
-
|
|
283
|
-
Args:
|
|
284
|
-
snapshot_id: The ID of the snapshot to load
|
|
285
|
-
|
|
286
|
-
Returns:
|
|
287
|
-
ContextSnapshot if found, None otherwise
|
|
288
|
-
"""
|
|
289
|
-
snapshots_dir = self.brain_path / "ledger" / "snapshots"
|
|
290
|
-
snapshot_file = snapshots_dir / f"{snapshot_id}.json"
|
|
291
|
-
|
|
292
|
-
if snapshot_file.exists():
|
|
293
|
-
try:
|
|
294
|
-
data = json.loads(snapshot_file.read_text())
|
|
295
|
-
return ContextSnapshot.from_dict(data)
|
|
296
|
-
except Exception:
|
|
297
|
-
pass
|
|
298
|
-
|
|
299
|
-
return None
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
# Singleton instance for convenience
|
|
303
|
-
_context_manager: Optional[ContextManager] = None
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
def get_context_manager() -> ContextManager:
|
|
307
|
-
"""Get or create the singleton ContextManager instance."""
|
|
308
|
-
global _context_manager
|
|
309
|
-
if _context_manager is None:
|
|
310
|
-
_context_manager = ContextManager()
|
|
311
|
-
return _context_manager
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
def compute_context_hash(recent_events: Optional[List[str]] = None) -> str:
|
|
315
|
-
"""
|
|
316
|
-
Convenience function to compute the current context hash.
|
|
317
|
-
|
|
318
|
-
Args:
|
|
319
|
-
recent_events: Optional list of recent event strings
|
|
320
|
-
|
|
321
|
-
Returns:
|
|
322
|
-
SHA-256 hash (truncated to 16 chars) of current world-state
|
|
323
|
-
"""
|
|
324
|
-
cm = get_context_manager()
|
|
325
|
-
state_hash, _ = cm.compute_world_state_hash(recent_events=recent_events)
|
|
326
|
-
return state_hash
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
def verify_turn_integrity(before_hash: str, after_hash: str,
|
|
330
|
-
allowed_drift: float = 0.0) -> bool:
|
|
331
|
-
"""
|
|
332
|
-
Simple verification that state hasn't drifted unexpectedly.
|
|
333
|
-
|
|
334
|
-
Args:
|
|
335
|
-
before_hash: Hash from before agent turn
|
|
336
|
-
after_hash: Hash from after agent turn
|
|
337
|
-
allowed_drift: Currently unused, reserved for fuzzy matching
|
|
338
|
-
|
|
339
|
-
Returns:
|
|
340
|
-
True if hashes match (no drift), False otherwise
|
|
341
|
-
"""
|
|
342
|
-
return before_hash == after_hash
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
[
|
|
2
|
-
{
|
|
3
|
-
"keywords": [
|
|
4
|
-
"deploy",
|
|
5
|
-
"cloud run",
|
|
6
|
-
"build",
|
|
7
|
-
"ship",
|
|
8
|
-
"production"
|
|
9
|
-
],
|
|
10
|
-
"inject": [
|
|
11
|
-
"cloudbuild.yaml",
|
|
12
|
-
"docs/infra/DEPLOYMENT.md",
|
|
13
|
-
"docs/infra/RELEASE_PROTOCOL.md"
|
|
14
|
-
],
|
|
15
|
-
"description": "Deployment and Infrastructure Context"
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
"keywords": [
|
|
19
|
-
"database",
|
|
20
|
-
"schema",
|
|
21
|
-
"sql",
|
|
22
|
-
"postgres",
|
|
23
|
-
"migration"
|
|
24
|
-
],
|
|
25
|
-
"inject": [
|
|
26
|
-
"docs/architecture/DATABASE_SCHEMA.md",
|
|
27
|
-
"alembic.ini"
|
|
28
|
-
],
|
|
29
|
-
"description": "Database and Storage Context"
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
"keywords": [
|
|
33
|
-
"brain",
|
|
34
|
-
"memory",
|
|
35
|
-
"context",
|
|
36
|
-
"recall",
|
|
37
|
-
"store"
|
|
38
|
-
],
|
|
39
|
-
"inject": [
|
|
40
|
-
"mcp-server-nucleus/src/mcp_server_nucleus/runtime/factory.py",
|
|
41
|
-
"docs/architecture/BRAIN_DESIGN.md"
|
|
42
|
-
],
|
|
43
|
-
"description": "Nucleus Brain Architecture Context"
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
"keywords": [
|
|
47
|
-
"api",
|
|
48
|
-
"endpoint",
|
|
49
|
-
"route",
|
|
50
|
-
"http",
|
|
51
|
-
"rest"
|
|
52
|
-
],
|
|
53
|
-
"inject": [
|
|
54
|
-
"docs/api/OPENAPI_SPEC.md",
|
|
55
|
-
"app.py"
|
|
56
|
-
],
|
|
57
|
-
"description": "API Surface Area Context"
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
"keywords": [
|
|
61
|
-
"ui",
|
|
62
|
-
"frontend",
|
|
63
|
-
"component",
|
|
64
|
-
"react",
|
|
65
|
-
"tailwind"
|
|
66
|
-
],
|
|
67
|
-
"inject": [
|
|
68
|
-
"tools/nucleus-hud/tailwind.config.ts",
|
|
69
|
-
"tools/nucleus-hud/app/globals.css"
|
|
70
|
-
],
|
|
71
|
-
"description": "Frontend Design System Context"
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
"keywords": [
|
|
75
|
-
"test",
|
|
76
|
-
"verify",
|
|
77
|
-
"qa",
|
|
78
|
-
"check"
|
|
79
|
-
],
|
|
80
|
-
"inject": [
|
|
81
|
-
"tests/README.md",
|
|
82
|
-
"NUCLEUS_GRAND_VERIFICATION_PLAN.md"
|
|
83
|
-
],
|
|
84
|
-
"description": "Testing and Verification Context"
|
|
85
|
-
}
|
|
86
|
-
]
|