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,517 +0,0 @@
|
|
|
1
|
-
from typing import Dict, Any, List
|
|
2
|
-
from .. import commitment_ledger
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
import os
|
|
5
|
-
import json
|
|
6
|
-
import hashlib
|
|
7
|
-
import uuid
|
|
8
|
-
from datetime import datetime, timezone
|
|
9
|
-
|
|
10
|
-
# v0.6.0 DSoR: Import context manager and IPC auth
|
|
11
|
-
from .context_manager import get_context_manager
|
|
12
|
-
from .ipc_auth import get_ipc_auth_manager, IPCToken
|
|
13
|
-
|
|
14
|
-
# Gemini types imported dynamically or duck-typed via DualEngineLLM
|
|
15
|
-
|
|
16
|
-
def get_brain_path_internal() -> Path:
|
|
17
|
-
"""Helper to get brain path inside runtime"""
|
|
18
|
-
return Path(os.getenv("NUCLEAR_BRAIN_PATH", "/Users/lokeshgarg/ai-mvp-backend/.brain"))
|
|
19
|
-
|
|
20
|
-
class DecisionMade:
|
|
21
|
-
"""
|
|
22
|
-
NOP v3.0: The "Why" Link.
|
|
23
|
-
Represents a sovereign decision with cryptographic anchoring.
|
|
24
|
-
v0.6.0 DSoR: Now emitted before every tool execution for full provenance.
|
|
25
|
-
"""
|
|
26
|
-
def __init__(self, decision_id: str, reasoning: str, context_hash: str, confidence: float = 1.0):
|
|
27
|
-
self.decision_id = decision_id
|
|
28
|
-
self.reasoning = reasoning
|
|
29
|
-
self.context_hash = context_hash
|
|
30
|
-
self.confidence = confidence
|
|
31
|
-
self.timestamp = datetime.now(timezone.utc).isoformat()
|
|
32
|
-
|
|
33
|
-
def to_dict(self) -> Dict[str, Any]:
|
|
34
|
-
"""Serialize for ledger storage"""
|
|
35
|
-
return {
|
|
36
|
-
"decision_id": self.decision_id,
|
|
37
|
-
"reasoning": self.reasoning,
|
|
38
|
-
"context_hash": self.context_hash,
|
|
39
|
-
"confidence": self.confidence,
|
|
40
|
-
"timestamp": self.timestamp
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
class ActionRequested:
|
|
44
|
-
"""
|
|
45
|
-
NOP v3.0: The "What" Linked to "Why".
|
|
46
|
-
"""
|
|
47
|
-
def __init__(self, action_id: str, decision_id: str, tool_name: str, args: Dict):
|
|
48
|
-
self.action_id = action_id
|
|
49
|
-
self.decision_id = decision_id
|
|
50
|
-
self.tool_name = tool_name
|
|
51
|
-
self.args = args
|
|
52
|
-
|
|
53
|
-
class EphemeralAgent:
|
|
54
|
-
"""
|
|
55
|
-
The Runtime.
|
|
56
|
-
A disposable agent that runs until completion.
|
|
57
|
-
MDR_005: Supports both LLM-driven (Smart) and Heuristic (Fast) modes.
|
|
58
|
-
MDR_002: Implements Active Correction (Critic) in LLM mode.
|
|
59
|
-
v0.6.0 DSoR: Emits DecisionMade events before every tool execution.
|
|
60
|
-
"""
|
|
61
|
-
def __init__(self, context: Dict[str, Any], model: Any = None):
|
|
62
|
-
self.context = context
|
|
63
|
-
self.model = model
|
|
64
|
-
self.history: List[str] = []
|
|
65
|
-
self.active = True
|
|
66
|
-
self._decision_ledger: List[DecisionMade] = [] # v0.6.0: Track all decisions
|
|
67
|
-
self._current_ipc_token: IPCToken = None # v0.6.0: Current IPC token for tool calls
|
|
68
|
-
self._context_manager = get_context_manager() # v0.6.0: For state verification
|
|
69
|
-
|
|
70
|
-
def _compute_context_hash(self, current_history: List[str]) -> str:
|
|
71
|
-
"""
|
|
72
|
-
v0.6.0 DSoR: Compute SHA-256 hash of current world-state.
|
|
73
|
-
Includes: persona, intent, and recent execution history.
|
|
74
|
-
"""
|
|
75
|
-
state_blob = json.dumps({
|
|
76
|
-
"persona": self.context.get('persona', 'unknown'),
|
|
77
|
-
"intent": self.context.get('intent', ''),
|
|
78
|
-
"history_tail": current_history[-5:] if current_history else [],
|
|
79
|
-
"tools_available": [t['name'] for t in self.context.get('tools', [])]
|
|
80
|
-
}, sort_keys=True)
|
|
81
|
-
return hashlib.sha256(state_blob.encode()).hexdigest()[:16]
|
|
82
|
-
|
|
83
|
-
def _emit_decision(self, tool_name: str, args: Dict, reasoning: str,
|
|
84
|
-
current_history: List[str], confidence: float = 0.9) -> DecisionMade:
|
|
85
|
-
"""
|
|
86
|
-
v0.6.0 DSoR: Emit and persist a DecisionMade event before tool execution.
|
|
87
|
-
This creates the cryptographic provenance trail required by the DSoR spec.
|
|
88
|
-
Also issues an IPC token linked to this decision for security.
|
|
89
|
-
"""
|
|
90
|
-
decision_id = f"dec-{uuid.uuid4().hex[:12]}"
|
|
91
|
-
context_hash = self._compute_context_hash(current_history)
|
|
92
|
-
|
|
93
|
-
decision = DecisionMade(
|
|
94
|
-
decision_id=decision_id,
|
|
95
|
-
reasoning=f"Tool: {tool_name} | {reasoning}",
|
|
96
|
-
context_hash=context_hash,
|
|
97
|
-
confidence=confidence
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
self._decision_ledger.append(decision)
|
|
101
|
-
|
|
102
|
-
# v0.6.0 DSoR: Issue IPC token linked to this decision
|
|
103
|
-
try:
|
|
104
|
-
ipc_manager = get_ipc_auth_manager()
|
|
105
|
-
token = ipc_manager.issue_token(
|
|
106
|
-
scope="tool_call",
|
|
107
|
-
decision_id=decision_id
|
|
108
|
-
)
|
|
109
|
-
# Store token for later use in _execute_tool
|
|
110
|
-
self._current_ipc_token = token
|
|
111
|
-
except Exception:
|
|
112
|
-
self._current_ipc_token = None
|
|
113
|
-
|
|
114
|
-
# Persist to decision log
|
|
115
|
-
try:
|
|
116
|
-
brain_path = get_brain_path_internal()
|
|
117
|
-
decisions_dir = brain_path / "ledger" / "decisions"
|
|
118
|
-
decisions_dir.mkdir(parents=True, exist_ok=True)
|
|
119
|
-
|
|
120
|
-
# Append to decisions.jsonl (append-only ledger)
|
|
121
|
-
decisions_file = decisions_dir / "decisions.jsonl"
|
|
122
|
-
with open(decisions_file, "a") as f:
|
|
123
|
-
f.write(json.dumps(decision.to_dict()) + "\n")
|
|
124
|
-
except Exception:
|
|
125
|
-
pass # Non-blocking persistence
|
|
126
|
-
|
|
127
|
-
return decision
|
|
128
|
-
|
|
129
|
-
async def run(self) -> str:
|
|
130
|
-
"""
|
|
131
|
-
Execute the agent loop.
|
|
132
|
-
Returns execution log.
|
|
133
|
-
v0.6.0 DSoR: Takes before/after snapshots for state verification.
|
|
134
|
-
"""
|
|
135
|
-
# MDR_010: Auto-record telemetry
|
|
136
|
-
try:
|
|
137
|
-
brain_path = get_brain_path_internal()
|
|
138
|
-
commitment_ledger.record_interaction(brain_path)
|
|
139
|
-
except Exception:
|
|
140
|
-
pass
|
|
141
|
-
|
|
142
|
-
log = []
|
|
143
|
-
log.append(f"--- Spawning Ephemeral Agent ({self.context['persona']}) ---")
|
|
144
|
-
log.append(f"Intent: {self.context['intent']}")
|
|
145
|
-
|
|
146
|
-
# v0.6.0 DSoR: Take "before" snapshot for state verification
|
|
147
|
-
before_snapshot = None
|
|
148
|
-
try:
|
|
149
|
-
before_snapshot = self._context_manager.take_snapshot(
|
|
150
|
-
metadata={
|
|
151
|
-
"agent_persona": self.context.get('persona'),
|
|
152
|
-
"agent_intent": self.context.get('intent'),
|
|
153
|
-
"snapshot_type": "before_run"
|
|
154
|
-
}
|
|
155
|
-
)
|
|
156
|
-
log.append(f"📸 State snapshot: {before_snapshot.snapshot_id} (pre-run)")
|
|
157
|
-
except Exception:
|
|
158
|
-
pass
|
|
159
|
-
|
|
160
|
-
# Execute the agent
|
|
161
|
-
if self.model:
|
|
162
|
-
result = await self._run_llm(log)
|
|
163
|
-
else:
|
|
164
|
-
result = self._run_heuristic(log)
|
|
165
|
-
|
|
166
|
-
# v0.6.0 DSoR: Take "after" snapshot and verify integrity
|
|
167
|
-
try:
|
|
168
|
-
after_snapshot = self._context_manager.take_snapshot(
|
|
169
|
-
metadata={
|
|
170
|
-
"agent_persona": self.context.get('persona'),
|
|
171
|
-
"decisions_made": len(self._decision_ledger),
|
|
172
|
-
"snapshot_type": "after_run"
|
|
173
|
-
}
|
|
174
|
-
)
|
|
175
|
-
|
|
176
|
-
if before_snapshot:
|
|
177
|
-
# Verify state integrity (allow ledger mutations from decisions)
|
|
178
|
-
verification = self._context_manager.verify_state_integrity(
|
|
179
|
-
before_snapshot,
|
|
180
|
-
after_snapshot,
|
|
181
|
-
allowed_mutations=["ledger", "recent_events"]
|
|
182
|
-
)
|
|
183
|
-
|
|
184
|
-
if not verification.is_valid:
|
|
185
|
-
# Log unexpected mutations (potential security issue)
|
|
186
|
-
log.append(f"⚠️ Unexpected state mutations: {verification.mutations_detected}")
|
|
187
|
-
else:
|
|
188
|
-
log.append(f"✅ State integrity verified (hash: {after_snapshot.state_hash})")
|
|
189
|
-
|
|
190
|
-
# Persist snapshots for audit
|
|
191
|
-
self._context_manager.persist_snapshot(before_snapshot)
|
|
192
|
-
self._context_manager.persist_snapshot(after_snapshot)
|
|
193
|
-
except Exception:
|
|
194
|
-
pass
|
|
195
|
-
|
|
196
|
-
return result
|
|
197
|
-
|
|
198
|
-
async def _run_llm(self, log: List[str]) -> str:
|
|
199
|
-
"""
|
|
200
|
-
MDR_005 / MDR_002: Real LLM Execution Loop with Critic & Multi-Turn Support
|
|
201
|
-
"""
|
|
202
|
-
log.append(">> Mode: LLM (Smart)")
|
|
203
|
-
|
|
204
|
-
# 1. Build Base Prompt
|
|
205
|
-
system_prompt = self.context.get('system_prompt', "You are an agent.")
|
|
206
|
-
|
|
207
|
-
tools_desc = []
|
|
208
|
-
for t in self.context['tools']:
|
|
209
|
-
tools_desc.append(f"### {t['name']}")
|
|
210
|
-
tools_desc.append(f"Description: {t['description']}")
|
|
211
|
-
tools_desc.append(f"Parameters: {json.dumps(t.get('parameters', {}), indent=2)}")
|
|
212
|
-
tools_desc.append("")
|
|
213
|
-
|
|
214
|
-
tools_block = "\n".join(tools_desc)
|
|
215
|
-
|
|
216
|
-
base_prompt = f"""{system_prompt}
|
|
217
|
-
|
|
218
|
-
AVAILABLE TOOLS:
|
|
219
|
-
{tools_block}
|
|
220
|
-
|
|
221
|
-
CRITICAL RULES (MDR_002):
|
|
222
|
-
1. You MUST call a tool to perform actions.
|
|
223
|
-
2. Do not just say you did it.
|
|
224
|
-
3. Output a JSON block with "tool" and "args" to call a tool.
|
|
225
|
-
Format:
|
|
226
|
-
```json
|
|
227
|
-
{{
|
|
228
|
-
"tool": "tool_name",
|
|
229
|
-
"args": {{ ... }}
|
|
230
|
-
}}
|
|
231
|
-
```
|
|
232
|
-
"""
|
|
233
|
-
# 2. Multi-Turn Loop
|
|
234
|
-
max_turns = 5
|
|
235
|
-
turn = 0
|
|
236
|
-
current_history = []
|
|
237
|
-
|
|
238
|
-
while turn < max_turns:
|
|
239
|
-
turn += 1
|
|
240
|
-
log.append(f"\n--- Turn {turn}/{max_turns} ---")
|
|
241
|
-
|
|
242
|
-
# Construct full prompt with history
|
|
243
|
-
history_str = "\n\n".join(current_history)
|
|
244
|
-
if history_str:
|
|
245
|
-
turn_prompt = f"{base_prompt}\n\n# EXECUTION HISTORY\n{history_str}\n\nNEXT STEP:"
|
|
246
|
-
else:
|
|
247
|
-
turn_prompt = base_prompt
|
|
248
|
-
|
|
249
|
-
try:
|
|
250
|
-
response = self.model.generate_content(turn_prompt)
|
|
251
|
-
|
|
252
|
-
# Defensive: Handle None or malformed response
|
|
253
|
-
if response is None:
|
|
254
|
-
log.append("[LLM Error]: Response is None (quota/network issue)")
|
|
255
|
-
current_history.append("SYSTEM: LLM returned no response. Retrying...")
|
|
256
|
-
continue
|
|
257
|
-
|
|
258
|
-
text = getattr(response, 'text', None)
|
|
259
|
-
if text is None:
|
|
260
|
-
# Try to extract from candidates
|
|
261
|
-
if hasattr(response, 'candidates') and response.candidates:
|
|
262
|
-
text = response.candidates[0].content.parts[0].text if response.candidates[0].content.parts else ""
|
|
263
|
-
else:
|
|
264
|
-
log.append("[LLM Error]: Response has no text content")
|
|
265
|
-
current_history.append("SYSTEM: LLM response malformed. Retrying...")
|
|
266
|
-
continue
|
|
267
|
-
|
|
268
|
-
log.append(f"[LLM Output]: {text[:500]}...")
|
|
269
|
-
current_history.append(f"AI: {text}")
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
# 3. Parse and Execute
|
|
273
|
-
import re
|
|
274
|
-
match = re.search(r'```json\s*(\{.*?\})\s*```', text, re.DOTALL)
|
|
275
|
-
|
|
276
|
-
if match:
|
|
277
|
-
tool_call = json.loads(match.group(1))
|
|
278
|
-
tool_name = tool_call.get("tool")
|
|
279
|
-
args = tool_call.get("args", {})
|
|
280
|
-
|
|
281
|
-
log.append(f">> Tool detected: {tool_name}")
|
|
282
|
-
|
|
283
|
-
# v0.6.0 DSoR: Emit DecisionMade BEFORE tool execution
|
|
284
|
-
decision = self._emit_decision(
|
|
285
|
-
tool_name=tool_name,
|
|
286
|
-
args=args,
|
|
287
|
-
reasoning=f"LLM requested tool call from turn {turn}",
|
|
288
|
-
current_history=current_history,
|
|
289
|
-
confidence=0.9
|
|
290
|
-
)
|
|
291
|
-
log.append(f"📋 Decision recorded: {decision.decision_id} (ctx:{decision.context_hash})")
|
|
292
|
-
|
|
293
|
-
result = self._execute_tool(tool_name, args)
|
|
294
|
-
log.append(f"[Tool Result]: {str(result)[:1000]}...")
|
|
295
|
-
current_history.append(f"TOOL_RESULT ({tool_name}): {result}")
|
|
296
|
-
else:
|
|
297
|
-
# No tool call - check if it's the final answer
|
|
298
|
-
if self.context['persona'] == 'Synthesizer' or "MISSION_COMPLETE" in text or "FINAL_ANSWER" in text:
|
|
299
|
-
log.append("✅ Mission complete signal detected.")
|
|
300
|
-
|
|
301
|
-
# GHOST COMPLETION FIX: Persist mission summary BEFORE returning
|
|
302
|
-
try:
|
|
303
|
-
brain_path = get_brain_path_internal()
|
|
304
|
-
import time as _time
|
|
305
|
-
timestamp = int(_time.time())
|
|
306
|
-
session_id = self.context.get('session_id', f'unknown_{timestamp}')
|
|
307
|
-
|
|
308
|
-
mission_dir = brain_path / "swarms" / session_id
|
|
309
|
-
mission_dir.mkdir(parents=True, exist_ok=True)
|
|
310
|
-
|
|
311
|
-
# Write summary.md
|
|
312
|
-
summary_path = mission_dir / "summary.md"
|
|
313
|
-
summary_content = f"""# Mission Summary
|
|
314
|
-
|
|
315
|
-
**Persona:** {self.context['persona']}
|
|
316
|
-
**Intent:** {self.context['intent']}
|
|
317
|
-
**Completed At:** {_time.strftime('%Y-%m-%dT%H:%M:%S')}
|
|
318
|
-
**Turns Used:** {turn}/{max_turns}
|
|
319
|
-
|
|
320
|
-
## Final Output
|
|
321
|
-
|
|
322
|
-
{text}
|
|
323
|
-
|
|
324
|
-
## Execution Log
|
|
325
|
-
|
|
326
|
-
```
|
|
327
|
-
{chr(10).join(log[-10:])}
|
|
328
|
-
```
|
|
329
|
-
"""
|
|
330
|
-
summary_path.write_text(summary_content)
|
|
331
|
-
|
|
332
|
-
# Write mission_log.json
|
|
333
|
-
log_path = mission_dir / "mission_log.json"
|
|
334
|
-
mission_data = {
|
|
335
|
-
"session_id": session_id,
|
|
336
|
-
"persona": self.context['persona'],
|
|
337
|
-
"intent": self.context['intent'],
|
|
338
|
-
"completed_at": _time.strftime('%Y-%m-%dT%H:%M:%S'),
|
|
339
|
-
"turns_used": turn,
|
|
340
|
-
"max_turns": max_turns,
|
|
341
|
-
"history": current_history[-10:],
|
|
342
|
-
"status": "COMPLETE"
|
|
343
|
-
}
|
|
344
|
-
log_path.write_text(json.dumps(mission_data, indent=2))
|
|
345
|
-
|
|
346
|
-
log.append(f"💾 Mission persisted to {mission_dir}")
|
|
347
|
-
except Exception as persist_error:
|
|
348
|
-
log.append(f"⚠️ Persistence warning: {persist_error}")
|
|
349
|
-
|
|
350
|
-
break
|
|
351
|
-
|
|
352
|
-
# MDR_002: THE ACTIVE CRITIC Intervention
|
|
353
|
-
log.append("⚠️ [CRITIC INTERVENTION] No tool call detected.")
|
|
354
|
-
|
|
355
|
-
critique_prompt = f"{turn_prompt}\n\nSYSTEM CRITIC: You did not call a tool! You MUST output a JSON tool call block or mark MISSION_COMPLETE if finished."
|
|
356
|
-
response_retry = self.model.generate_content(critique_prompt)
|
|
357
|
-
text_retry = response_retry.text
|
|
358
|
-
log.append(f"[LLM Retry Output]: {text_retry[:500]}...")
|
|
359
|
-
current_history.append(f"AI (Retry): {text_retry}")
|
|
360
|
-
|
|
361
|
-
match_retry = re.search(r'```json\s*(\{.*?\})\s*```', text_retry, re.DOTALL)
|
|
362
|
-
if match_retry:
|
|
363
|
-
tool_call = json.loads(match_retry.group(1))
|
|
364
|
-
tool_name = tool_call.get("tool")
|
|
365
|
-
args = tool_call.get("args", {})
|
|
366
|
-
|
|
367
|
-
log.append(f">> Tool detected (after critique): {tool_name}")
|
|
368
|
-
|
|
369
|
-
# v0.6.0 DSoR: Emit DecisionMade BEFORE tool execution (post-critique)
|
|
370
|
-
decision = self._emit_decision(
|
|
371
|
-
tool_name=tool_name,
|
|
372
|
-
args=args,
|
|
373
|
-
reasoning=f"LLM tool call after critic intervention, turn {turn}",
|
|
374
|
-
current_history=current_history,
|
|
375
|
-
confidence=0.75 # Lower confidence after needing correction
|
|
376
|
-
)
|
|
377
|
-
log.append(f"📋 Decision recorded: {decision.decision_id} (ctx:{decision.context_hash})")
|
|
378
|
-
|
|
379
|
-
result = self._execute_tool(tool_name, args)
|
|
380
|
-
log.append(f"[Tool Result]: {str(result)[:1000]}...")
|
|
381
|
-
current_history.append(f"TOOL_RESULT ({tool_name}): {result}")
|
|
382
|
-
else:
|
|
383
|
-
# Bug 5 Fix: Persist findings even on tool-call failure
|
|
384
|
-
log.append("❌ Agent failed to call tool after critique.")
|
|
385
|
-
|
|
386
|
-
# Save orphan output before terminating
|
|
387
|
-
try:
|
|
388
|
-
brain_path = get_brain_path_internal()
|
|
389
|
-
orphan_dir = brain_path / "swarms" / "orphan_outputs"
|
|
390
|
-
orphan_dir.mkdir(parents=True, exist_ok=True)
|
|
391
|
-
|
|
392
|
-
import time as _time
|
|
393
|
-
timestamp = int(_time.time())
|
|
394
|
-
output_file = orphan_dir / f"critic_failure_{self.context['persona']}_{timestamp}.md"
|
|
395
|
-
|
|
396
|
-
findings = f"""# Orphan Agent Output
|
|
397
|
-
|
|
398
|
-
**Persona:** {self.context['persona']}
|
|
399
|
-
**Intent:** {self.context['intent']}
|
|
400
|
-
**Timestamp:** {timestamp}
|
|
401
|
-
|
|
402
|
-
## Agent Analysis (Not Persisted via Tool)
|
|
403
|
-
|
|
404
|
-
{text_retry}
|
|
405
|
-
|
|
406
|
-
## Execution History
|
|
407
|
-
|
|
408
|
-
```
|
|
409
|
-
{chr(10).join(current_history[-5:])}
|
|
410
|
-
```
|
|
411
|
-
"""
|
|
412
|
-
output_file.write_text(findings)
|
|
413
|
-
log.append(f"💾 Orphan output saved to {output_file}")
|
|
414
|
-
except Exception as persist_error:
|
|
415
|
-
log.append(f"⚠️ Failed to save orphan output: {persist_error}")
|
|
416
|
-
|
|
417
|
-
break
|
|
418
|
-
|
|
419
|
-
except Exception as e:
|
|
420
|
-
log.append(f"LLM Error: {e}")
|
|
421
|
-
break
|
|
422
|
-
|
|
423
|
-
return "\n".join(log)
|
|
424
|
-
|
|
425
|
-
def _run_heuristic(self, log: List[str]) -> str:
|
|
426
|
-
"""Legacy Heuristic Mode - v0.6.0 DSoR: Also emits DecisionMade events"""
|
|
427
|
-
log.append(">> Mode: Heuristic (Fast)")
|
|
428
|
-
|
|
429
|
-
full_intent = self.context['intent'].lower()
|
|
430
|
-
executed = False
|
|
431
|
-
heuristic_history = [f"Intent: {self.context['intent']}"]
|
|
432
|
-
|
|
433
|
-
# 1. BRAIN OPS
|
|
434
|
-
if "brain" in full_intent or "task" in full_intent or "scan" in full_intent:
|
|
435
|
-
# Heuristic mapping for heuristic mode
|
|
436
|
-
pass
|
|
437
|
-
|
|
438
|
-
# 2. RENDER OPS
|
|
439
|
-
if "deploy" in full_intent or "check" in full_intent or "list" in full_intent:
|
|
440
|
-
if "render_list_services" in [t['name'] for t in self.context['tools']] and ("list" in full_intent or "check" in full_intent):
|
|
441
|
-
# v0.6.0 DSoR: Emit DecisionMade before heuristic tool call
|
|
442
|
-
decision = self._emit_decision(
|
|
443
|
-
tool_name="render_list_services",
|
|
444
|
-
args={},
|
|
445
|
-
reasoning="Heuristic mode: intent matched 'list' or 'check'",
|
|
446
|
-
current_history=heuristic_history,
|
|
447
|
-
confidence=0.7 # Heuristic = lower confidence
|
|
448
|
-
)
|
|
449
|
-
log.append(f"📋 Decision recorded: {decision.decision_id}")
|
|
450
|
-
|
|
451
|
-
result = self._execute_tool("render_list_services", {})
|
|
452
|
-
log.append(">> [Heuristic] Calling render_list_services...")
|
|
453
|
-
log.append(result)
|
|
454
|
-
executed = True
|
|
455
|
-
elif "render_deploy_service" in [t['name'] for t in self.context['tools']] and "deploy" in full_intent:
|
|
456
|
-
# v0.6.0 DSoR: Emit DecisionMade before heuristic tool call
|
|
457
|
-
decision = self._emit_decision(
|
|
458
|
-
tool_name="render_list_services",
|
|
459
|
-
args={},
|
|
460
|
-
reasoning="Heuristic mode: deploy intent, listing first for safety",
|
|
461
|
-
current_history=heuristic_history,
|
|
462
|
-
confidence=0.6 # Even lower - we're being cautious
|
|
463
|
-
)
|
|
464
|
-
log.append(f"📋 Decision recorded: {decision.decision_id}")
|
|
465
|
-
|
|
466
|
-
result = self._execute_tool("render_list_services", {})
|
|
467
|
-
log.append(">> [Heuristic] Intent detected deploy, listing services first...")
|
|
468
|
-
log.append(result)
|
|
469
|
-
executed = True
|
|
470
|
-
|
|
471
|
-
if not executed:
|
|
472
|
-
log.append("No heuristic action map found.")
|
|
473
|
-
|
|
474
|
-
log.append("--- Agent Terminated ---")
|
|
475
|
-
return "\n".join(log)
|
|
476
|
-
|
|
477
|
-
def _execute_tool(self, tool_name: str, args: Dict) -> str:
|
|
478
|
-
"""
|
|
479
|
-
Execute a tool with v0.6.0 DSoR security.
|
|
480
|
-
Validates and consumes IPC token before execution.
|
|
481
|
-
"""
|
|
482
|
-
# v0.6.0 DSoR: Validate and consume IPC token if present
|
|
483
|
-
if self._current_ipc_token:
|
|
484
|
-
try:
|
|
485
|
-
ipc_manager = get_ipc_auth_manager()
|
|
486
|
-
is_valid, error = ipc_manager.validate_token(
|
|
487
|
-
self._current_ipc_token.token_id,
|
|
488
|
-
scope="tool_call"
|
|
489
|
-
)
|
|
490
|
-
|
|
491
|
-
if is_valid:
|
|
492
|
-
# Compute request hash for audit trail
|
|
493
|
-
request_hash = hashlib.sha256(
|
|
494
|
-
json.dumps({"tool": tool_name, "args": args}, sort_keys=True).encode()
|
|
495
|
-
).hexdigest()[:16]
|
|
496
|
-
|
|
497
|
-
# Consume token and record metering
|
|
498
|
-
ipc_manager.consume_token(
|
|
499
|
-
self._current_ipc_token.token_id,
|
|
500
|
-
request_hash=request_hash,
|
|
501
|
-
resource_type="tool_call",
|
|
502
|
-
units=1.0,
|
|
503
|
-
metadata={"tool": tool_name}
|
|
504
|
-
)
|
|
505
|
-
# Clear token after use (single-use)
|
|
506
|
-
self._current_ipc_token = None
|
|
507
|
-
except Exception:
|
|
508
|
-
pass # Non-blocking security layer
|
|
509
|
-
|
|
510
|
-
# Find the capability that owns this tool
|
|
511
|
-
caps = self.context.get('capability_instances', [])
|
|
512
|
-
for cap in caps:
|
|
513
|
-
tools = [t['name'] for t in cap.get_tools()]
|
|
514
|
-
if tool_name in tools:
|
|
515
|
-
return cap.execute_tool(tool_name, args)
|
|
516
|
-
|
|
517
|
-
return f"Error: Tool {tool_name} implementation not found."
|