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,126 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
ContextBroker: The Economy Engine.
|
|
3
|
-
Facilitates the exchange of information (context) negotiation between agents.
|
|
4
|
-
|
|
5
|
-
Roles:
|
|
6
|
-
1. MARKETPLACE: Where agents list what they know.
|
|
7
|
-
2. CLEARING HOUSE: Records transactions (who bought what).
|
|
8
|
-
3. SETTLEMENT: (Future) Handles credit transfers.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
import json
|
|
12
|
-
import logging
|
|
13
|
-
import uuid
|
|
14
|
-
from datetime import datetime
|
|
15
|
-
from typing import List, Optional, Dict, Literal
|
|
16
|
-
from pathlib import Path
|
|
17
|
-
from pydantic import BaseModel
|
|
18
|
-
|
|
19
|
-
from .locking import get_lock
|
|
20
|
-
|
|
21
|
-
logger = logging.getLogger("BROKER")
|
|
22
|
-
|
|
23
|
-
class ContextListing(BaseModel):
|
|
24
|
-
id: str
|
|
25
|
-
provider_id: str
|
|
26
|
-
topic: str
|
|
27
|
-
description: str
|
|
28
|
-
content: str # In MVP, content is stored directly. In prod, this is a pointer.
|
|
29
|
-
price: float = 0.0
|
|
30
|
-
type: Literal["data", "service"] = "data"
|
|
31
|
-
created_at: str
|
|
32
|
-
|
|
33
|
-
class ContextTransaction(BaseModel):
|
|
34
|
-
id: str
|
|
35
|
-
listing_id: str
|
|
36
|
-
buyer_id: str
|
|
37
|
-
seller_id: str
|
|
38
|
-
amount: float
|
|
39
|
-
timestamp: str
|
|
40
|
-
content: Optional[str] = None # The delivered goods
|
|
41
|
-
|
|
42
|
-
class ContextBroker:
|
|
43
|
-
def __init__(self, brain_path: Path):
|
|
44
|
-
self.brain_path = brain_path
|
|
45
|
-
self.listings_path = brain_path / "ledger" / "listings.json"
|
|
46
|
-
self.ledger_path = brain_path / "ledger" / "transactions.jsonl"
|
|
47
|
-
|
|
48
|
-
# Ensure dirs
|
|
49
|
-
self.listings_path.parent.mkdir(parents=True, exist_ok=True)
|
|
50
|
-
|
|
51
|
-
def _load_listings(self) -> Dict[str, ContextListing]:
|
|
52
|
-
with get_lock("broker", self.brain_path).section():
|
|
53
|
-
if not self.listings_path.exists():
|
|
54
|
-
return {}
|
|
55
|
-
try:
|
|
56
|
-
data = json.loads(self.listings_path.read_text())
|
|
57
|
-
return {k: ContextListing(**v) for k, v in data.items()}
|
|
58
|
-
except Exception:
|
|
59
|
-
return {}
|
|
60
|
-
|
|
61
|
-
def _save_listings(self, listings: Dict[str, ContextListing]):
|
|
62
|
-
with get_lock("broker", self.brain_path).section():
|
|
63
|
-
data = {k: v.model_dump() for k, v in listings.items()}
|
|
64
|
-
self.listings_path.write_text(json.dumps(data, indent=2))
|
|
65
|
-
|
|
66
|
-
def publish_listing(self, provider_id: str, topic: str, description: str, content: str, price: float = 0.0, type: Literal["data", "service"] = "data") -> str:
|
|
67
|
-
"""Create a new context listing."""
|
|
68
|
-
listing_id = f"list-{uuid.uuid4().hex[:8]}"
|
|
69
|
-
listing = ContextListing(
|
|
70
|
-
id=listing_id,
|
|
71
|
-
provider_id=provider_id,
|
|
72
|
-
topic=topic,
|
|
73
|
-
description=description,
|
|
74
|
-
content=content,
|
|
75
|
-
price=price,
|
|
76
|
-
type=type,
|
|
77
|
-
created_at=datetime.now().isoformat()
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
listings = self._load_listings()
|
|
81
|
-
listings[listing_id] = listing
|
|
82
|
-
self._save_listings(listings)
|
|
83
|
-
|
|
84
|
-
logger.info(f"📢 Listing Published: {topic} by {provider_id} ({listing_id})")
|
|
85
|
-
return listing_id
|
|
86
|
-
|
|
87
|
-
def search_listings(self, query: str) -> List[ContextListing]:
|
|
88
|
-
"""Find listings by topic or description."""
|
|
89
|
-
listings = self._load_listings()
|
|
90
|
-
query = query.lower()
|
|
91
|
-
results = []
|
|
92
|
-
for lst in listings.values():
|
|
93
|
-
if query in lst.topic.lower() or query in lst.description.lower():
|
|
94
|
-
results.append(lst)
|
|
95
|
-
return results
|
|
96
|
-
|
|
97
|
-
def buy_context(self, buyer_id: str, listing_id: str) -> Optional[ContextTransaction]:
|
|
98
|
-
"""Execute a context transaction."""
|
|
99
|
-
listings = self._load_listings()
|
|
100
|
-
|
|
101
|
-
if listing_id not in listings:
|
|
102
|
-
logger.error(f"❌ Transaction Failed: Listing {listing_id} not found")
|
|
103
|
-
return None
|
|
104
|
-
|
|
105
|
-
listing = listings[listing_id]
|
|
106
|
-
|
|
107
|
-
# In a real economy, check budget here.
|
|
108
|
-
|
|
109
|
-
tx_id = f"tx-{uuid.uuid4().hex[:8]}"
|
|
110
|
-
tx = ContextTransaction(
|
|
111
|
-
id=tx_id,
|
|
112
|
-
listing_id=listing.id,
|
|
113
|
-
buyer_id=buyer_id,
|
|
114
|
-
seller_id=listing.provider_id,
|
|
115
|
-
amount=listing.price,
|
|
116
|
-
timestamp=datetime.now().isoformat(),
|
|
117
|
-
content=listing.content # Deliver the goods
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
# Append to ledger
|
|
121
|
-
with get_lock("ledger", self.brain_path).section():
|
|
122
|
-
with open(self.ledger_path, "a") as f:
|
|
123
|
-
f.write(tx.model_dump_json() + "\n")
|
|
124
|
-
|
|
125
|
-
logger.info(f"💰 Sold: {listing.topic} from {listing.provider_id} to {buyer_id}")
|
|
126
|
-
return tx
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
BudgetAuditor: The Financial Fuse.
|
|
3
|
-
Strictly enforces spending limits for Nucleus operations.
|
|
4
|
-
|
|
5
|
-
Strategic Role:
|
|
6
|
-
- Tracks cumulative spend across sessions.
|
|
7
|
-
- "Hard Fuse": Cuts off execution if budget exceeded.
|
|
8
|
-
- Uses BrainLock for accurate accounting.
|
|
9
|
-
- Supports Per-Agent BudgetGuard.
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
import json
|
|
13
|
-
import logging
|
|
14
|
-
from datetime import datetime
|
|
15
|
-
from pathlib import Path
|
|
16
|
-
from typing import Dict, Any, List
|
|
17
|
-
from .locking import get_lock
|
|
18
|
-
from .capabilities.base import Capability
|
|
19
|
-
|
|
20
|
-
logger = logging.getLogger("BUDGET_AUDITOR")
|
|
21
|
-
|
|
22
|
-
class BudgetAuditor:
|
|
23
|
-
def __init__(self, brain_path: Path):
|
|
24
|
-
self.brain_path = brain_path
|
|
25
|
-
self.ledger_path = brain_path / "ledger" / "budget_ledger.json"
|
|
26
|
-
|
|
27
|
-
# Ensure ledger dir exists
|
|
28
|
-
self.ledger_path.parent.mkdir(parents=True, exist_ok=True)
|
|
29
|
-
|
|
30
|
-
def _load_ledger(self) -> Dict:
|
|
31
|
-
if self.ledger_path.exists():
|
|
32
|
-
try:
|
|
33
|
-
return json.loads(self.ledger_path.read_text())
|
|
34
|
-
except Exception:
|
|
35
|
-
pass
|
|
36
|
-
return {
|
|
37
|
-
"total_spend_usd": 0.0,
|
|
38
|
-
"daily_spend_usd": {}, # "YYYY-MM-DD": float
|
|
39
|
-
"agent_spend_usd": {}, # "str(agent_id)": float (Cumulative)
|
|
40
|
-
"transactions": []
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
def _save_ledger(self, ledger: Dict):
|
|
44
|
-
self.ledger_path.write_text(json.dumps(ledger, indent=2))
|
|
45
|
-
|
|
46
|
-
def check_authorization(self, completed_cost: float = 0.0) -> bool:
|
|
47
|
-
"""
|
|
48
|
-
Global check (Not agent specific).
|
|
49
|
-
For now, just a placeholder or daily limit check.
|
|
50
|
-
"""
|
|
51
|
-
return True
|
|
52
|
-
|
|
53
|
-
def record_expense(self, amount_usd: float, description: str, source: str = "llm"):
|
|
54
|
-
"""Record a completed expense globally and per-agent"""
|
|
55
|
-
with get_lock("budget", self.brain_path).section():
|
|
56
|
-
ledger = self._load_ledger()
|
|
57
|
-
today = datetime.now().strftime("%Y-%m-%d")
|
|
58
|
-
|
|
59
|
-
ledger["total_spend_usd"] += amount_usd
|
|
60
|
-
ledger["daily_spend_usd"][today] = ledger["daily_spend_usd"].get(today, 0.0) + amount_usd
|
|
61
|
-
|
|
62
|
-
# If source is an agent ID, track per agent
|
|
63
|
-
if source:
|
|
64
|
-
ledger["agent_spend_usd"][source] = ledger["agent_spend_usd"].get(source, 0.0) + amount_usd
|
|
65
|
-
|
|
66
|
-
ledger["transactions"].append({
|
|
67
|
-
"timestamp": datetime.now().isoformat(),
|
|
68
|
-
"amount": amount_usd,
|
|
69
|
-
"description": description,
|
|
70
|
-
"source": source
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
# Trim transactions if too long
|
|
74
|
-
if len(ledger["transactions"]) > 1000:
|
|
75
|
-
ledger["transactions"] = ledger["transactions"][-1000:]
|
|
76
|
-
|
|
77
|
-
self._save_ledger(ledger)
|
|
78
|
-
|
|
79
|
-
def get_agent_spend(self, agent_id: str) -> float:
|
|
80
|
-
"""Get total spend for a specific agent"""
|
|
81
|
-
ledger = self._load_ledger()
|
|
82
|
-
return ledger.get("agent_spend_usd", {}).get(agent_id, 0.0)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
class BudgetGuard(Capability):
|
|
86
|
-
"""
|
|
87
|
-
Wraps an imported capability to enforce strict budget limits for a specific agent.
|
|
88
|
-
"Host Policy overrides Agent Policy".
|
|
89
|
-
"""
|
|
90
|
-
def __init__(self, inner: Capability, auditor: BudgetAuditor, agent_id: str, max_budget_usd: float = 0.0):
|
|
91
|
-
# Initialize Abstract Base Class
|
|
92
|
-
# Note: We don't call super().__init__() because ABCs don't need it,
|
|
93
|
-
# but we must ensure we don't violate property requirements.
|
|
94
|
-
self.inner = inner
|
|
95
|
-
self.auditor = auditor
|
|
96
|
-
self.agent_id = agent_id
|
|
97
|
-
self.max_budget_usd = max_budget_usd
|
|
98
|
-
|
|
99
|
-
# Load initial spend state
|
|
100
|
-
self.spent_usd = self.auditor.get_agent_spend(agent_id)
|
|
101
|
-
|
|
102
|
-
@property
|
|
103
|
-
def name(self) -> str:
|
|
104
|
-
return self.inner.name
|
|
105
|
-
|
|
106
|
-
@property
|
|
107
|
-
def description(self) -> str:
|
|
108
|
-
return f"{self.inner.description} (Budget: ${self.max_budget_usd})"
|
|
109
|
-
|
|
110
|
-
def get_tools(self) -> List[Dict[str, Any]]:
|
|
111
|
-
# We might want to wrap tool descriptions too, but for now we delegate
|
|
112
|
-
return self.inner.get_tools()
|
|
113
|
-
|
|
114
|
-
def execute(self, params: Dict[str, Any]) -> str:
|
|
115
|
-
# Sync simple spending state (in case other tools updated it)
|
|
116
|
-
self.spent_usd = self.auditor.get_agent_spend(self.agent_id)
|
|
117
|
-
|
|
118
|
-
# 1. Pre-Check (Hard Fuse)
|
|
119
|
-
if self.spent_usd >= self.max_budget_usd and self.max_budget_usd >= 0:
|
|
120
|
-
# Note: We can treat max_budget_usd < 0 as infinite if we wanted,
|
|
121
|
-
# but for now let's say >=0 is required.
|
|
122
|
-
if self.max_budget_usd == 0:
|
|
123
|
-
return "❌ SECURITY BLOCK: Zero Cost Budget Enforced."
|
|
124
|
-
|
|
125
|
-
# Allow execution if we are extremely close (floating point epsilon)
|
|
126
|
-
# OR if we want to strict block. Let's strict block.
|
|
127
|
-
return f"❌ SECURITY BLOCK: Budget Exceeded (${self.spent_usd:.4f}/${self.max_budget_usd:.2f})"
|
|
128
|
-
|
|
129
|
-
# 2. Execute Inner Tool
|
|
130
|
-
logger.info(f"💰 BudgetGuard Allowing {self.agent_id} call (Spent: ${self.spent_usd})")
|
|
131
|
-
try:
|
|
132
|
-
result = self.inner.execute(params)
|
|
133
|
-
except Exception as e:
|
|
134
|
-
# Even if it fails, did it cost money?
|
|
135
|
-
# For network tools/LLM, yes. For local function errors, maybe not.
|
|
136
|
-
# We assume "Attempted Execution" costs standard rate for now.
|
|
137
|
-
result = f"Error: {e}"
|
|
138
|
-
|
|
139
|
-
# 3. Post-Check (Record Cost)
|
|
140
|
-
# In a real system, we'd measure token usage or network bytes.
|
|
141
|
-
# For simulation/Phase 57, we assume a flat rate per tool call.
|
|
142
|
-
COST_PER_CALL = 0.01
|
|
143
|
-
|
|
144
|
-
self.auditor.record_expense(
|
|
145
|
-
amount_usd=COST_PER_CALL,
|
|
146
|
-
description=f"Action: {self.inner.name}",
|
|
147
|
-
source=self.agent_id
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
# Update local tracking
|
|
151
|
-
self.spent_usd += COST_PER_CALL
|
|
152
|
-
|
|
153
|
-
return result
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
from abc import ABC, abstractmethod
|
|
2
|
-
from typing import List, Dict, Any
|
|
3
|
-
|
|
4
|
-
class Capability(ABC):
|
|
5
|
-
@property
|
|
6
|
-
@abstractmethod
|
|
7
|
-
def name(self) -> str:
|
|
8
|
-
"""Name of the capability cluster (e.g., 'render_ops')."""
|
|
9
|
-
pass
|
|
10
|
-
|
|
11
|
-
@property
|
|
12
|
-
@abstractmethod
|
|
13
|
-
def description(self) -> str:
|
|
14
|
-
"""Description for the Context Factory."""
|
|
15
|
-
pass
|
|
16
|
-
|
|
17
|
-
@abstractmethod
|
|
18
|
-
def get_tools(self) -> List[Dict[str, Any]]:
|
|
19
|
-
"""Return list of MCP tool definitions."""
|
|
20
|
-
pass
|
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
from typing import List, Dict, Any
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
import os
|
|
4
|
-
from .base import Capability
|
|
5
|
-
from ... import commitment_ledger
|
|
6
|
-
|
|
7
|
-
class BrainOps(Capability):
|
|
8
|
-
@property
|
|
9
|
-
def name(self) -> str:
|
|
10
|
-
return "brain_ops"
|
|
11
|
-
|
|
12
|
-
@property
|
|
13
|
-
def description(self) -> str:
|
|
14
|
-
return "Tools for managing the centralized commitment ledger (The Brain)."
|
|
15
|
-
|
|
16
|
-
def get_tools(self) -> List[Dict[str, Any]]:
|
|
17
|
-
return [
|
|
18
|
-
{
|
|
19
|
-
"name": "brain_add_commitment",
|
|
20
|
-
"description": "Add a new commitment (task, todo, loop) to the ledger.",
|
|
21
|
-
"parameters": {
|
|
22
|
-
"type": "object",
|
|
23
|
-
"properties": {
|
|
24
|
-
"description": {"type": "string"},
|
|
25
|
-
"loop_type": {"type": "string", "enum": ["task", "todo", "draft", "decision"]},
|
|
26
|
-
"priority": {"type": "integer", "minimum": 1, "maximum": 5},
|
|
27
|
-
"source": {"type": "string"}
|
|
28
|
-
},
|
|
29
|
-
"required": ["description"]
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
"name": "brain_get_open_loops",
|
|
34
|
-
"description": "Get all active open loops.",
|
|
35
|
-
"parameters": {
|
|
36
|
-
"type": "object",
|
|
37
|
-
"properties": {
|
|
38
|
-
"type_filter": {"type": "string"}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
"name": "brain_scan_commitments",
|
|
44
|
-
"description": "Trigger the Librarian to scan artifacts for checklist items.",
|
|
45
|
-
"parameters": {"type": "object", "properties": {}}
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
"name": "brain_archive_stale",
|
|
49
|
-
"description": "Trigger the Librarian to archive stale commitments.",
|
|
50
|
-
"parameters": {"type": "object", "properties": {}}
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
"name": "brain_orchestrate_swarm",
|
|
54
|
-
"description": "Initialize a multi-agent swarm for a complex mission.",
|
|
55
|
-
"parameters": {
|
|
56
|
-
"type": "object",
|
|
57
|
-
"properties": {
|
|
58
|
-
"mission": {"type": "string"},
|
|
59
|
-
"agents": {"type": "array", "items": {"type": "string"}},
|
|
60
|
-
"swarm_type": {"type": "string", "enum": ["genesis", "execution"]}
|
|
61
|
-
},
|
|
62
|
-
"required": ["mission"]
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
"name": "brain_export",
|
|
67
|
-
"description": "Export the brain content to a zip file (respecting .brainignore).",
|
|
68
|
-
"parameters": {"type": "object", "properties": {}}
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
"name": "brain_consolidate_logs",
|
|
72
|
-
"description": "Consolidate raw JSON logs in .brain/raw/ into a daily archive.",
|
|
73
|
-
"parameters": {"type": "object", "properties": {}}
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
"name": "brain_delegate_task",
|
|
77
|
-
"description": "Delegate a task to another specialized agent persona. The agent will execute autonomously.",
|
|
78
|
-
"parameters": {
|
|
79
|
-
"type": "object",
|
|
80
|
-
"properties": {
|
|
81
|
-
"persona": {"type": "string", "description": "Target persona: librarian, researcher, critic, developer, architect, strategist, devops, synthesizer"},
|
|
82
|
-
"intent": {"type": "string", "description": "The task description for the delegated agent"}
|
|
83
|
-
},
|
|
84
|
-
"required": ["persona", "intent"]
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
]
|
|
88
|
-
|
|
89
|
-
def execute_tool(self, tool_name: str, args: Dict) -> str:
|
|
90
|
-
"""Execute the tool locally using commitment_ledger."""
|
|
91
|
-
# Use configured brain path
|
|
92
|
-
brain_path = Path(os.environ.get("NUCLEUS_BRAIN_PATH", "/Users/lokeshgarg/ai-mvp-backend/.brain"))
|
|
93
|
-
|
|
94
|
-
if tool_name == "brain_add_commitment":
|
|
95
|
-
result = commitment_ledger.add_commitment(
|
|
96
|
-
brain_path=brain_path,
|
|
97
|
-
source_file="NAR_AGENT",
|
|
98
|
-
source_line=0,
|
|
99
|
-
description=args['description'],
|
|
100
|
-
comm_type=args.get('loop_type', 'task'),
|
|
101
|
-
priority=args.get('priority', 3),
|
|
102
|
-
source=args.get('source', 'nar_agent')
|
|
103
|
-
)
|
|
104
|
-
return f"Commitment Added: {result['id']}"
|
|
105
|
-
|
|
106
|
-
elif tool_name == "brain_get_open_loops":
|
|
107
|
-
loops = commitment_ledger.load_ledger(brain_path)["commitments"]
|
|
108
|
-
# Filter logic could go here
|
|
109
|
-
return f"Found {len([c for c in loops if c['status']=='open'])} open loops."
|
|
110
|
-
|
|
111
|
-
elif tool_name == "brain_scan_commitments":
|
|
112
|
-
result = commitment_ledger.scan_for_commitments(brain_path)
|
|
113
|
-
return f"Scan Complete: {result}"
|
|
114
|
-
|
|
115
|
-
elif tool_name == "brain_archive_stale":
|
|
116
|
-
count = commitment_ledger.auto_archive_stale(brain_path)
|
|
117
|
-
return f"Archived {count} stale items."
|
|
118
|
-
|
|
119
|
-
elif tool_name == "brain_export":
|
|
120
|
-
# Call export logic (implemented in commitment_ledger for centralization)
|
|
121
|
-
if hasattr(commitment_ledger, 'export_brain'):
|
|
122
|
-
result = commitment_ledger.export_brain(brain_path)
|
|
123
|
-
return f"Export Complete: {result}"
|
|
124
|
-
return "Error: export_brain not implemented in ledger."
|
|
125
|
-
|
|
126
|
-
elif tool_name == "brain_delegate_task":
|
|
127
|
-
# Dynamic import to avoid circular dependency
|
|
128
|
-
from uuid import uuid4
|
|
129
|
-
from ..factory import ContextFactory
|
|
130
|
-
from ..agent import EphemeralAgent
|
|
131
|
-
from ..llm_client import DualEngineLLM
|
|
132
|
-
|
|
133
|
-
persona = args.get('persona')
|
|
134
|
-
intent = args.get('intent')
|
|
135
|
-
|
|
136
|
-
# Use factory to create specialized agent
|
|
137
|
-
session_id = f"delegated-{str(uuid4())[:8]}"
|
|
138
|
-
factory = ContextFactory()
|
|
139
|
-
|
|
140
|
-
# Create context for the specific persona
|
|
141
|
-
context = factory.create_context_for_persona(session_id, persona, intent)
|
|
142
|
-
|
|
143
|
-
# Execute
|
|
144
|
-
llm = DualEngineLLM()
|
|
145
|
-
agent = EphemeralAgent(context, model=llm)
|
|
146
|
-
|
|
147
|
-
# In a real async environment, we'd await this.
|
|
148
|
-
# But execute_tool is currently sync in this base class signature.
|
|
149
|
-
# We need to run it synchronously or change the signature.
|
|
150
|
-
# Using asyncio.run() for now as a bridge.
|
|
151
|
-
# Using asyncio.run() or existing loop via nest_asyncio
|
|
152
|
-
import asyncio
|
|
153
|
-
try:
|
|
154
|
-
import nest_asyncio
|
|
155
|
-
nest_asyncio.apply()
|
|
156
|
-
except ImportError:
|
|
157
|
-
pass
|
|
158
|
-
|
|
159
|
-
try:
|
|
160
|
-
# With nest_asyncio, we can just use asyncio.run or loop.run_until_complete
|
|
161
|
-
# even if a loop is running.
|
|
162
|
-
loop = asyncio.get_event_loop()
|
|
163
|
-
if loop.is_running():
|
|
164
|
-
log = loop.run_until_complete(agent.run())
|
|
165
|
-
else:
|
|
166
|
-
log = asyncio.run(agent.run())
|
|
167
|
-
|
|
168
|
-
return f"✅ Delegation Complete:\n{log}"
|
|
169
|
-
|
|
170
|
-
except RuntimeError:
|
|
171
|
-
# Fallback if nest_asyncio failed or not installed and loop is running
|
|
172
|
-
return f"⚠️ Cannot block on delegation (Async Loop Running & nest_asyncio missing). Task queued for {persona}: {intent}"
|
|
173
|
-
|
|
174
|
-
elif tool_name == "brain_orchestrate_swarm":
|
|
175
|
-
from ..orchestrator import SwarmsOrchestrator
|
|
176
|
-
import asyncio
|
|
177
|
-
|
|
178
|
-
orchestrator = SwarmsOrchestrator(brain_path=brain_path)
|
|
179
|
-
|
|
180
|
-
# Extract agents list from args
|
|
181
|
-
agents = args.get('agents', None) # List of agent personas
|
|
182
|
-
|
|
183
|
-
# start_mission is async, need to run it properly
|
|
184
|
-
try:
|
|
185
|
-
import nest_asyncio
|
|
186
|
-
nest_asyncio.apply()
|
|
187
|
-
except ImportError:
|
|
188
|
-
pass
|
|
189
|
-
|
|
190
|
-
try:
|
|
191
|
-
loop = asyncio.get_event_loop()
|
|
192
|
-
if loop.is_running():
|
|
193
|
-
result = loop.run_until_complete(orchestrator.start_mission(
|
|
194
|
-
mission_goal=args['mission'],
|
|
195
|
-
swarm_type=args.get('swarm_type', 'genesis'),
|
|
196
|
-
agents=agents # Pass agents to orchestrator
|
|
197
|
-
))
|
|
198
|
-
else:
|
|
199
|
-
result = asyncio.run(orchestrator.start_mission(
|
|
200
|
-
mission_goal=args['mission'],
|
|
201
|
-
swarm_type=args.get('swarm_type', 'genesis'),
|
|
202
|
-
agents=agents
|
|
203
|
-
))
|
|
204
|
-
|
|
205
|
-
return f"✅ Swarm Initiated:\\nMission ID: {result.get('mission_id', 'unknown')}\\nAgents: {agents or 'auto-detected'}\\nStatus: {result.get('status', 'started')}"
|
|
206
|
-
except RuntimeError as e:
|
|
207
|
-
return f"⚠️ Swarm start failed (async issue): {e}. Mission queued."
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
elif tool_name == "brain_consolidate_logs":
|
|
211
|
-
# Consolidate raw logs into daily archives
|
|
212
|
-
import json
|
|
213
|
-
from datetime import datetime
|
|
214
|
-
|
|
215
|
-
brain_path = Path(os.environ.get("NUCLEUS_BRAIN_PATH", "/Users/lokeshgarg/ai-mvp-backend/.brain"))
|
|
216
|
-
raw_path = brain_path / "raw"
|
|
217
|
-
archive_dir = brain_path / "archive"
|
|
218
|
-
archive_dir.mkdir(parents=True, exist_ok=True)
|
|
219
|
-
|
|
220
|
-
if not raw_path.exists():
|
|
221
|
-
return "No raw logs to consolidate."
|
|
222
|
-
|
|
223
|
-
logs = list(raw_path.glob("*.json"))
|
|
224
|
-
if not logs:
|
|
225
|
-
return "0 logs found. Nothing to consolidate."
|
|
226
|
-
|
|
227
|
-
# Group by date
|
|
228
|
-
count = 0
|
|
229
|
-
size_saved = 0
|
|
230
|
-
|
|
231
|
-
# Use current date for the archive bucket
|
|
232
|
-
# Ideally we group by the timestamp in the file, but for V1 just dumping current pile is fine.
|
|
233
|
-
today = datetime.now().strftime("%Y-%m-%d")
|
|
234
|
-
archive_file = archive_dir / f"raw_interactions_{today}.jsonl"
|
|
235
|
-
|
|
236
|
-
with open(archive_file, "a") as out_f:
|
|
237
|
-
for log_file in logs:
|
|
238
|
-
try:
|
|
239
|
-
content = log_file.read_text()
|
|
240
|
-
# Minify JSON line
|
|
241
|
-
data = json.loads(content)
|
|
242
|
-
out_f.write(json.dumps(data) + "\n")
|
|
243
|
-
|
|
244
|
-
size_saved += log_file.stat().st_size
|
|
245
|
-
log_file.unlink() # Delete original
|
|
246
|
-
count += 1
|
|
247
|
-
except Exception as e:
|
|
248
|
-
print(f"Error consolidating {log_file}: {e}")
|
|
249
|
-
|
|
250
|
-
return f"✅ Consolidated {count} raw logs into {archive_file.name}. Saved {size_saved/1024:.2f} KB of inode space."
|
|
251
|
-
|
|
252
|
-
return f"Tool {tool_name} not found in BrainOps."
|
|
253
|
-
|