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.
Files changed (135) hide show
  1. package/package.json +2 -4
  2. package/.github/workflows/ci.yml +0 -72
  3. package/.smithery/shttp/module.js +0 -3
  4. package/.smithery/shttp/module.js.map +0 -7
  5. package/.smithery/stdio/index.cjs +0 -74
  6. package/.smithery/stdio/index.cjs.map +0 -7
  7. package/CHANGELOG.md +0 -65
  8. package/CODE_OF_CONDUCT.md +0 -34
  9. package/COMPARISON.md +0 -20
  10. package/CONTRIBUTING.md +0 -137
  11. package/DEVELOPMENT.md +0 -109
  12. package/LICENSE +0 -21
  13. package/README.md +0 -319
  14. package/ROADMAP.md +0 -24
  15. package/SECURITY.md +0 -88
  16. package/docs/COMMUNITY_FAQ.md +0 -48
  17. package/docs/COMPETITIVE_ANALYSIS.md +0 -90
  18. package/docs/DEMO_SCRIPT.md +0 -86
  19. package/docs/ENTERPRISE.md +0 -169
  20. package/docs/FAQ.md +0 -228
  21. package/docs/QUICK_START.md +0 -155
  22. package/docs/README.md +0 -67
  23. package/docs/RECURSIVE_SNAP_DEMO.gif +0 -0
  24. package/docs/social-preview-hq.png +0 -0
  25. package/docs/social-preview.png +0 -0
  26. package/examples/basic_usage.py +0 -84
  27. package/package.json.real +0 -24
  28. package/pyproject.toml +0 -50
  29. package/registry-server.json +0 -20
  30. package/scripts/demo_governance.py +0 -109
  31. package/scripts/sse_bridge.py +0 -87
  32. package/scripts/verify_windows_paths.py +0 -48
  33. package/server.json +0 -20
  34. package/src/mcp_server_nucleus/__init__.py +0 -8583
  35. package/src/mcp_server_nucleus/__main__.py +0 -9
  36. package/src/mcp_server_nucleus/cli.py +0 -1225
  37. package/src/mcp_server_nucleus/commitment_core.py +0 -146
  38. package/src/mcp_server_nucleus/commitment_ledger.py +0 -841
  39. package/src/mcp_server_nucleus/core/__init__.py +0 -6
  40. package/src/mcp_server_nucleus/core/orchestrator.py +0 -9
  41. package/src/mcp_server_nucleus/core/tool_registration_impl.py +0 -99
  42. package/src/mcp_server_nucleus/daemon/safety/lock.py +0 -121
  43. package/src/mcp_server_nucleus/deployment/render_integration.py +0 -4
  44. package/src/mcp_server_nucleus/hypervisor/__init__.py +0 -0
  45. package/src/mcp_server_nucleus/hypervisor/injector.py +0 -83
  46. package/src/mcp_server_nucleus/hypervisor/locker.py +0 -130
  47. package/src/mcp_server_nucleus/hypervisor/watchdog.py +0 -71
  48. package/src/mcp_server_nucleus/runtime/__init__.py +0 -58
  49. package/src/mcp_server_nucleus/runtime/agent.py +0 -517
  50. package/src/mcp_server_nucleus/runtime/agent_pool.py +0 -797
  51. package/src/mcp_server_nucleus/runtime/agents/base.py +0 -34
  52. package/src/mcp_server_nucleus/runtime/agents/oracle/manifest.json +0 -28
  53. package/src/mcp_server_nucleus/runtime/auth.py +0 -98
  54. package/src/mcp_server_nucleus/runtime/autopilot.py +0 -1057
  55. package/src/mcp_server_nucleus/runtime/broker.py +0 -126
  56. package/src/mcp_server_nucleus/runtime/budget.py +0 -153
  57. package/src/mcp_server_nucleus/runtime/capabilities/base.py +0 -20
  58. package/src/mcp_server_nucleus/runtime/capabilities/brain_ops.py +0 -253
  59. package/src/mcp_server_nucleus/runtime/capabilities/code_ops.py +0 -192
  60. package/src/mcp_server_nucleus/runtime/capabilities/depth_tracker.py +0 -81
  61. package/src/mcp_server_nucleus/runtime/capabilities/feature_map.py +0 -274
  62. package/src/mcp_server_nucleus/runtime/capabilities/marketing_engine.py +0 -205
  63. package/src/mcp_server_nucleus/runtime/capabilities/memory_ops.py +0 -79
  64. package/src/mcp_server_nucleus/runtime/capabilities/proof_system.py +0 -127
  65. package/src/mcp_server_nucleus/runtime/capabilities/render_ops.py +0 -80
  66. package/src/mcp_server_nucleus/runtime/capabilities/render_poller_cap.py +0 -266
  67. package/src/mcp_server_nucleus/runtime/capabilities/self_healing.py +0 -110
  68. package/src/mcp_server_nucleus/runtime/capabilities/strategy.py +0 -138
  69. package/src/mcp_server_nucleus/runtime/capabilities/synthesizer.py +0 -134
  70. package/src/mcp_server_nucleus/runtime/capabilities/web_ops.py +0 -94
  71. package/src/mcp_server_nucleus/runtime/common.py +0 -134
  72. package/src/mcp_server_nucleus/runtime/context_manager.py +0 -342
  73. package/src/mcp_server_nucleus/runtime/context_rules.json +0 -86
  74. package/src/mcp_server_nucleus/runtime/crdt_task_store.py +0 -349
  75. package/src/mcp_server_nucleus/runtime/critic.py +0 -150
  76. package/src/mcp_server_nucleus/runtime/daemon.py +0 -142
  77. package/src/mcp_server_nucleus/runtime/dashboard.py +0 -1087
  78. package/src/mcp_server_nucleus/runtime/depth_ops.py +0 -339
  79. package/src/mcp_server_nucleus/runtime/event_ops.py +0 -103
  80. package/src/mcp_server_nucleus/runtime/event_stream.py +0 -213
  81. package/src/mcp_server_nucleus/runtime/export.py +0 -64
  82. package/src/mcp_server_nucleus/runtime/factory.py +0 -594
  83. package/src/mcp_server_nucleus/runtime/federation.py +0 -1040
  84. package/src/mcp_server_nucleus/runtime/fetcher.py +0 -92
  85. package/src/mcp_server_nucleus/runtime/file_monitor.py +0 -208
  86. package/src/mcp_server_nucleus/runtime/firestore_bridge.py +0 -91
  87. package/src/mcp_server_nucleus/runtime/gcloud_ops.py +0 -186
  88. package/src/mcp_server_nucleus/runtime/hooks.py +0 -246
  89. package/src/mcp_server_nucleus/runtime/identity/gatekeeper.py +0 -106
  90. package/src/mcp_server_nucleus/runtime/identity/keygen.py +0 -147
  91. package/src/mcp_server_nucleus/runtime/identity/manifest.py +0 -62
  92. package/src/mcp_server_nucleus/runtime/identity/trust.py +0 -27
  93. package/src/mcp_server_nucleus/runtime/inspector.py +0 -74
  94. package/src/mcp_server_nucleus/runtime/installer.py +0 -81
  95. package/src/mcp_server_nucleus/runtime/ipc_auth.py +0 -377
  96. package/src/mcp_server_nucleus/runtime/lifecycle.py +0 -129
  97. package/src/mcp_server_nucleus/runtime/llm_client.py +0 -408
  98. package/src/mcp_server_nucleus/runtime/llm_tiers.json +0 -88
  99. package/src/mcp_server_nucleus/runtime/locking.py +0 -228
  100. package/src/mcp_server_nucleus/runtime/loops/fixer.py +0 -110
  101. package/src/mcp_server_nucleus/runtime/memory.py +0 -158
  102. package/src/mcp_server_nucleus/runtime/mounter.py +0 -210
  103. package/src/mcp_server_nucleus/runtime/mounter_ops.py +0 -250
  104. package/src/mcp_server_nucleus/runtime/nuke_protocol.py +0 -147
  105. package/src/mcp_server_nucleus/runtime/orchestrator.py +0 -370
  106. package/src/mcp_server_nucleus/runtime/orchestrator_unified.py +0 -263
  107. package/src/mcp_server_nucleus/runtime/orchestrator_v3.py +0 -617
  108. package/src/mcp_server_nucleus/runtime/plugin_loader.py +0 -84
  109. package/src/mcp_server_nucleus/runtime/policy.py +0 -117
  110. package/src/mcp_server_nucleus/runtime/profiling.py +0 -219
  111. package/src/mcp_server_nucleus/runtime/prometheus.py +0 -286
  112. package/src/mcp_server_nucleus/runtime/proposals.py +0 -101
  113. package/src/mcp_server_nucleus/runtime/publisher.py +0 -109
  114. package/src/mcp_server_nucleus/runtime/pulse.py +0 -516
  115. package/src/mcp_server_nucleus/runtime/registry.py +0 -82
  116. package/src/mcp_server_nucleus/runtime/render_ops.py +0 -83
  117. package/src/mcp_server_nucleus/runtime/schema_gen.py +0 -140
  118. package/src/mcp_server_nucleus/runtime/session_ops.py +0 -224
  119. package/src/mcp_server_nucleus/runtime/stdio_server.py +0 -633
  120. package/src/mcp_server_nucleus/runtime/storage.py +0 -83
  121. package/src/mcp_server_nucleus/runtime/strategy.py +0 -91
  122. package/src/mcp_server_nucleus/runtime/swarm.py +0 -170
  123. package/src/mcp_server_nucleus/runtime/sync_ops.py +0 -878
  124. package/src/mcp_server_nucleus/runtime/task_ingestion.py +0 -1020
  125. package/src/mcp_server_nucleus/runtime/task_ops.py +0 -449
  126. package/src/mcp_server_nucleus/runtime/task_scheduler.py +0 -488
  127. package/src/mcp_server_nucleus/runtime/team.py +0 -59
  128. package/src/mcp_server_nucleus/runtime/triggers.py +0 -237
  129. package/src/mcp_server_nucleus/runtime/vector_store.py +0 -125
  130. package/src/mcp_server_nucleus/runtime/watcher.py +0 -112
  131. package/src/mcp_server_nucleus/tool_tiers.py +0 -243
  132. package/tests/__init__.py +0 -1
  133. package/tests/conftest.py +0 -42
  134. package/tests/test_core.py +0 -235
  135. 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."