@neuroverseos/nv-sim 0.1.9 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +187 -535
- package/connectors/nv_mirofish_wrapper.py +841 -0
- package/connectors/nv_scienceclaw_wrapper.py +453 -0
- package/dist/adapters/scienceclaw.js +52 -2
- package/dist/assets/index-CH_VswRM.css +1 -0
- package/dist/assets/index-sT4b_z7w.js +686 -0
- package/dist/assets/{reportEngine-D2ZrMny8.js → reportEngine-Bu8bB5Yq.js} +1 -1
- package/dist/connectors/nv-scienceclaw-post.js +363 -0
- package/dist/engine/aiProvider.js +82 -3
- package/dist/engine/analyzer.js +12 -24
- package/dist/engine/cli.js +89 -114
- package/dist/engine/dynamicsGovernance.js +4 -0
- package/dist/engine/fullGovernedLoop.js +16 -1
- package/dist/engine/goalEngine.js +3 -4
- package/dist/engine/governance.js +18 -0
- package/dist/engine/index.js +19 -28
- package/dist/engine/intentTranslator.js +281 -0
- package/dist/engine/liveAdapter.js +100 -18
- package/dist/engine/liveVisualizer.js +2071 -1023
- package/dist/engine/primeRadiant.js +2 -8
- package/dist/engine/reasoningEngine.js +2 -7
- package/dist/engine/scenarioCapsule.js +5 -5
- package/dist/engine/swarmSimulation.js +1 -9
- package/dist/engine/universalAdapter.js +371 -0
- package/dist/engine/worldBridge.js +22 -8
- package/dist/index.html +2 -2
- package/dist/lib/reasoningEngine.js +17 -1
- package/dist/lib/simulationAdapter.js +11 -11
- package/dist/lib/swarmParser.js +1 -1
- package/dist/runtime/govern.js +160 -7
- package/dist/runtime/index.js +1 -4
- package/dist/runtime/types.js +91 -0
- package/package.json +23 -6
- package/dist/adapters/mirofish.js +0 -461
- package/dist/assets/index-B64NuIXu.css +0 -1
- package/dist/assets/index-BMkPevVr.js +0 -532
- package/dist/assets/mirotir-logo-DUexumBH.svg +0 -185
- package/dist/engine/mirofish.js +0 -295
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
NeuroVerse ScienceClaw Connector — Governed Execution Wrapper
|
|
4
|
+
|
|
5
|
+
Governance layer:
|
|
6
|
+
Level 1 (entry gate): Evaluates the invocation BEFORE ScienceClaw starts
|
|
7
|
+
Level 2 (streaming): Parses stdout, evaluates each artifact/cycle through /api/evaluate
|
|
8
|
+
Level 3 (completion): Reports final state back to governance server
|
|
9
|
+
|
|
10
|
+
Instead of:
|
|
11
|
+
scienceclaw-post --agent MyAgent --topic "CRISPR risks"
|
|
12
|
+
|
|
13
|
+
Run:
|
|
14
|
+
python nv_scienceclaw_wrapper.py --agent MyAgent --topic "CRISPR risks"
|
|
15
|
+
|
|
16
|
+
What happens:
|
|
17
|
+
1. Evaluates the launch invocation (ALLOW/BLOCK/PENALIZE/REWARD)
|
|
18
|
+
2. If allowed, spawns scienceclaw-post as subprocess
|
|
19
|
+
3. Reads stdout line-by-line — parses JSON artifact data
|
|
20
|
+
4. Each artifact/cycle is evaluated through /api/evaluate
|
|
21
|
+
5. If governance blocks mid-stream, can terminate the process
|
|
22
|
+
6. Reports completion back to governance server
|
|
23
|
+
|
|
24
|
+
Requires:
|
|
25
|
+
- NeuroVerse governance server running: nv-sim serve
|
|
26
|
+
- scienceclaw-post on PATH (or set SCIENCECLAW_BIN)
|
|
27
|
+
- pip install requests
|
|
28
|
+
|
|
29
|
+
Zero edits to ScienceClaw required.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
import subprocess
|
|
33
|
+
import sys
|
|
34
|
+
import os
|
|
35
|
+
import json
|
|
36
|
+
import time
|
|
37
|
+
import signal
|
|
38
|
+
import threading
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
import requests
|
|
42
|
+
except ImportError:
|
|
43
|
+
print("ERROR: 'requests' package required. Install with: pip install requests")
|
|
44
|
+
sys.exit(1)
|
|
45
|
+
|
|
46
|
+
# ============================================
|
|
47
|
+
# CONFIGURATION
|
|
48
|
+
# ============================================
|
|
49
|
+
|
|
50
|
+
NEUROVERSE_URL = os.environ.get("NEUROVERSE_URL", "http://localhost:3456")
|
|
51
|
+
EVALUATE_ENDPOINT = f"{NEUROVERSE_URL}/api/evaluate"
|
|
52
|
+
SCIENCECLAW_BIN = os.environ.get("SCIENCECLAW_BIN", "scienceclaw-post")
|
|
53
|
+
VERBOSE = os.environ.get("NV_VERBOSE", "0") == "1"
|
|
54
|
+
|
|
55
|
+
# Streaming governance: if True, kill process on BLOCK/PENALIZE mid-stream
|
|
56
|
+
ENFORCE_STREAMING = os.environ.get("NV_ENFORCE_STREAMING", "1") == "1"
|
|
57
|
+
|
|
58
|
+
# ============================================
|
|
59
|
+
# STATS
|
|
60
|
+
# ============================================
|
|
61
|
+
|
|
62
|
+
stats = {
|
|
63
|
+
"evaluated": 0,
|
|
64
|
+
"allowed": 0,
|
|
65
|
+
"blocked": 0,
|
|
66
|
+
"penalized": 0,
|
|
67
|
+
"rewarded": 0,
|
|
68
|
+
"modified": 0,
|
|
69
|
+
"cycles_seen": 0,
|
|
70
|
+
"artifacts_seen": 0,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# ============================================
|
|
74
|
+
# ARGUMENT PARSING
|
|
75
|
+
# ============================================
|
|
76
|
+
|
|
77
|
+
def parse_args(argv):
|
|
78
|
+
"""Extract key arguments from the scienceclaw-post command line."""
|
|
79
|
+
args = argv[1:]
|
|
80
|
+
parsed = {
|
|
81
|
+
"agent": "unknown",
|
|
82
|
+
"topic": "",
|
|
83
|
+
"skill": None,
|
|
84
|
+
"confidence": None,
|
|
85
|
+
"passthrough_args": list(args),
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
i = 0
|
|
89
|
+
nv_args = set()
|
|
90
|
+
while i < len(args):
|
|
91
|
+
flag = args[i]
|
|
92
|
+
if flag == "--agent" and i + 1 < len(args):
|
|
93
|
+
parsed["agent"] = args[i + 1]
|
|
94
|
+
i += 2
|
|
95
|
+
elif flag == "--topic" and i + 1 < len(args):
|
|
96
|
+
parsed["topic"] = args[i + 1]
|
|
97
|
+
i += 2
|
|
98
|
+
elif flag == "--skill" and i + 1 < len(args):
|
|
99
|
+
parsed["skill"] = args[i + 1]
|
|
100
|
+
i += 2
|
|
101
|
+
elif flag == "--confidence" and i + 1 < len(args):
|
|
102
|
+
parsed["confidence"] = float(args[i + 1])
|
|
103
|
+
i += 2
|
|
104
|
+
elif flag == "--scienceclaw-bin" and i + 1 < len(args):
|
|
105
|
+
nv_args.add(i)
|
|
106
|
+
nv_args.add(i + 1)
|
|
107
|
+
global SCIENCECLAW_BIN
|
|
108
|
+
SCIENCECLAW_BIN = args[i + 1]
|
|
109
|
+
i += 2
|
|
110
|
+
elif flag == "--nv-url" and i + 1 < len(args):
|
|
111
|
+
nv_args.add(i)
|
|
112
|
+
nv_args.add(i + 1)
|
|
113
|
+
global NEUROVERSE_URL, EVALUATE_ENDPOINT
|
|
114
|
+
NEUROVERSE_URL = args[i + 1]
|
|
115
|
+
EVALUATE_ENDPOINT = f"{NEUROVERSE_URL}/api/evaluate"
|
|
116
|
+
i += 2
|
|
117
|
+
elif flag == "--nv-verbose":
|
|
118
|
+
nv_args.add(i)
|
|
119
|
+
global VERBOSE
|
|
120
|
+
VERBOSE = True
|
|
121
|
+
i += 1
|
|
122
|
+
elif flag == "--nv-no-stream":
|
|
123
|
+
nv_args.add(i)
|
|
124
|
+
global ENFORCE_STREAMING
|
|
125
|
+
ENFORCE_STREAMING = False
|
|
126
|
+
i += 1
|
|
127
|
+
else:
|
|
128
|
+
i += 1
|
|
129
|
+
|
|
130
|
+
parsed["passthrough_args"] = [
|
|
131
|
+
a for idx, a in enumerate(args) if idx not in nv_args
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
return parsed
|
|
135
|
+
|
|
136
|
+
# ============================================
|
|
137
|
+
# GOVERNANCE EVALUATION
|
|
138
|
+
# ============================================
|
|
139
|
+
|
|
140
|
+
def evaluate_action(action_type, agent, payload_extra=None):
|
|
141
|
+
"""Call NeuroVerse /api/evaluate and return the verdict."""
|
|
142
|
+
payload = {
|
|
143
|
+
"actor": agent,
|
|
144
|
+
"action": action_type,
|
|
145
|
+
"payload": {
|
|
146
|
+
"description": f"Agent {agent} executing {action_type}",
|
|
147
|
+
},
|
|
148
|
+
"world": "science_research",
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if payload_extra:
|
|
152
|
+
payload["payload"].update(payload_extra)
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
resp = requests.post(EVALUATE_ENDPOINT, json=payload, timeout=5)
|
|
156
|
+
resp.raise_for_status()
|
|
157
|
+
result = resp.json()
|
|
158
|
+
stats["evaluated"] += 1
|
|
159
|
+
|
|
160
|
+
decision = result.get("decision", result.get("status", "ALLOW")).upper()
|
|
161
|
+
if decision == "ALLOW":
|
|
162
|
+
stats["allowed"] += 1
|
|
163
|
+
elif decision == "BLOCK":
|
|
164
|
+
stats["blocked"] += 1
|
|
165
|
+
elif decision == "PENALIZE":
|
|
166
|
+
stats["penalized"] += 1
|
|
167
|
+
elif decision == "REWARD":
|
|
168
|
+
stats["rewarded"] += 1
|
|
169
|
+
elif decision == "MODIFY":
|
|
170
|
+
stats["modified"] += 1
|
|
171
|
+
|
|
172
|
+
if VERBOSE:
|
|
173
|
+
print(f" [NV] {action_type} -> {decision}: {result.get('reason', '')}")
|
|
174
|
+
|
|
175
|
+
return result
|
|
176
|
+
except requests.ConnectionError:
|
|
177
|
+
if stats["evaluated"] == 0:
|
|
178
|
+
print(f" [NV] WARNING: NeuroVerse server not reachable at {NEUROVERSE_URL}")
|
|
179
|
+
print(f" [NV] Start it with: nv-sim serve")
|
|
180
|
+
print(f" [NV] Falling back to ALLOW (ungoverned mode)\n")
|
|
181
|
+
return {"decision": "ALLOW", "reason": "Governance server unavailable"}
|
|
182
|
+
except Exception as e:
|
|
183
|
+
return {"decision": "ALLOW", "reason": f"Evaluation error: {e}"}
|
|
184
|
+
|
|
185
|
+
# ============================================
|
|
186
|
+
# STREAMING GOVERNANCE — parse + evaluate output
|
|
187
|
+
# ============================================
|
|
188
|
+
|
|
189
|
+
def govern_output_line(line, agent, proc):
|
|
190
|
+
"""Parse a structured JSON line from ScienceClaw and evaluate for governance.
|
|
191
|
+
|
|
192
|
+
Schema-gated: only strict JSON lines are governed.
|
|
193
|
+
Non-JSON output is passed through without evaluation (by design).
|
|
194
|
+
No regex or heuristic parsing — safety and determinism over flexibility.
|
|
195
|
+
"""
|
|
196
|
+
line = line.strip()
|
|
197
|
+
if not line:
|
|
198
|
+
return
|
|
199
|
+
if not line.startswith("{"):
|
|
200
|
+
if VERBOSE:
|
|
201
|
+
print(f" [NV] [ungoverned: not_json] {line[:120]}")
|
|
202
|
+
return
|
|
203
|
+
|
|
204
|
+
try:
|
|
205
|
+
data = json.loads(line)
|
|
206
|
+
except json.JSONDecodeError:
|
|
207
|
+
if VERBOSE:
|
|
208
|
+
print(f" [NV] [ungoverned: invalid_json] {line[:120]}")
|
|
209
|
+
return
|
|
210
|
+
|
|
211
|
+
# ── Schema validation: require cycle + artifacts ──
|
|
212
|
+
cycle = data.get("cycle") or data.get("step") or data.get("round")
|
|
213
|
+
artifacts = data.get("artifacts") or data.get("agent_actions") or []
|
|
214
|
+
|
|
215
|
+
if cycle is None:
|
|
216
|
+
if VERBOSE:
|
|
217
|
+
print(f" [NV] [ungoverned: missing_cycle] {line[:120]}")
|
|
218
|
+
return
|
|
219
|
+
|
|
220
|
+
if not artifacts:
|
|
221
|
+
if VERBOSE:
|
|
222
|
+
print(f" [NV] [ungoverned: no_artifacts] cycle={cycle}")
|
|
223
|
+
return
|
|
224
|
+
|
|
225
|
+
# ── Schema-valid: cycle + artifacts present ──
|
|
226
|
+
stats["cycles_seen"] += 1
|
|
227
|
+
|
|
228
|
+
# Evaluate each artifact in the cycle
|
|
229
|
+
for art in artifacts:
|
|
230
|
+
stats["artifacts_seen"] += 1
|
|
231
|
+
art_agent = art.get("agent_id") or art.get("id") or agent
|
|
232
|
+
art_type = art.get("type") or art.get("action") or "publish"
|
|
233
|
+
art_desc = art.get("description") or f"{art_type} artifact"
|
|
234
|
+
art_conf = art.get("confidence")
|
|
235
|
+
|
|
236
|
+
verdict = evaluate_action(
|
|
237
|
+
action_type=f"publish_{art_type}",
|
|
238
|
+
agent=art_agent,
|
|
239
|
+
payload_extra={
|
|
240
|
+
"description": art_desc,
|
|
241
|
+
"artifact_type": art_type,
|
|
242
|
+
"confidence": art_conf,
|
|
243
|
+
"reproduced": art.get("reproduced", False),
|
|
244
|
+
"citations": art.get("citations", 0),
|
|
245
|
+
"cycle": cycle,
|
|
246
|
+
},
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
decision = verdict.get("decision", verdict.get("status", "ALLOW")).upper()
|
|
250
|
+
|
|
251
|
+
if decision in ("BLOCK", "PENALIZE") and ENFORCE_STREAMING:
|
|
252
|
+
reason = verdict.get("reason", "policy violation")
|
|
253
|
+
print(f"\n [NV] GOVERNANCE HALT at cycle {cycle}")
|
|
254
|
+
print(f" Artifact: {art_desc}")
|
|
255
|
+
print(f" Agent: {art_agent}")
|
|
256
|
+
print(f" Decision: {decision}")
|
|
257
|
+
print(f" Reason: {reason}")
|
|
258
|
+
|
|
259
|
+
if decision == "PENALIZE" and verdict.get("consequence"):
|
|
260
|
+
c = verdict["consequence"]
|
|
261
|
+
print(f" Consequence: {c.get('type', 'unknown')} for {c.get('rounds', 1)} round(s)")
|
|
262
|
+
|
|
263
|
+
print(f" Terminating ScienceClaw process...\n")
|
|
264
|
+
|
|
265
|
+
try:
|
|
266
|
+
proc.terminate()
|
|
267
|
+
proc.wait(timeout=5)
|
|
268
|
+
except Exception:
|
|
269
|
+
proc.kill()
|
|
270
|
+
return # stop processing
|
|
271
|
+
|
|
272
|
+
elif decision == "REWARD":
|
|
273
|
+
reward = verdict.get("reward", {})
|
|
274
|
+
if VERBOSE:
|
|
275
|
+
print(f" [NV] Cycle {cycle}: REWARDED {art_agent} — {reward.get('description', '')}")
|
|
276
|
+
|
|
277
|
+
return
|
|
278
|
+
|
|
279
|
+
# ── Single artifact (no cycle wrapper) ──
|
|
280
|
+
if data.get("agent_id") and data.get("type"):
|
|
281
|
+
stats["artifacts_seen"] += 1
|
|
282
|
+
art_agent = data.get("agent_id") or agent
|
|
283
|
+
art_type = data.get("type") or "publish"
|
|
284
|
+
|
|
285
|
+
evaluate_action(
|
|
286
|
+
action_type=f"publish_{art_type}",
|
|
287
|
+
agent=art_agent,
|
|
288
|
+
payload_extra={
|
|
289
|
+
"description": data.get("description", f"{art_type} artifact"),
|
|
290
|
+
"artifact_type": art_type,
|
|
291
|
+
"confidence": data.get("confidence"),
|
|
292
|
+
"reproduced": data.get("reproduced", False),
|
|
293
|
+
},
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def stream_stderr(proc):
|
|
298
|
+
"""Read stderr in a background thread to prevent blocking."""
|
|
299
|
+
for line in iter(proc.stderr.readline, ""):
|
|
300
|
+
if line.strip():
|
|
301
|
+
sys.stderr.write(line)
|
|
302
|
+
sys.stderr.flush()
|
|
303
|
+
|
|
304
|
+
# ============================================
|
|
305
|
+
# MAIN EXECUTION
|
|
306
|
+
# ============================================
|
|
307
|
+
|
|
308
|
+
def run():
|
|
309
|
+
parsed = parse_args(sys.argv)
|
|
310
|
+
agent = parsed["agent"]
|
|
311
|
+
topic = parsed["topic"]
|
|
312
|
+
|
|
313
|
+
print(f"\n NeuroVerse ScienceClaw Connector")
|
|
314
|
+
print(f" {'=' * 50}")
|
|
315
|
+
print(f" Agent: {agent}")
|
|
316
|
+
print(f" Topic: {topic or '(none)'}")
|
|
317
|
+
if parsed["skill"]:
|
|
318
|
+
print(f" Skill: {parsed['skill']}")
|
|
319
|
+
print(f" Server: {NEUROVERSE_URL}")
|
|
320
|
+
print(f" Streaming: {'ON — each artifact evaluated' if ENFORCE_STREAMING else 'OFF — entry gate only'}")
|
|
321
|
+
print(f" {'=' * 50}\n")
|
|
322
|
+
|
|
323
|
+
# ── Level 1: Entry gate — evaluate the invocation ──
|
|
324
|
+
print(f" [1/4] Evaluating launch governance...")
|
|
325
|
+
|
|
326
|
+
action_type = "run_investigation"
|
|
327
|
+
if parsed["skill"]:
|
|
328
|
+
action_type = f"execute_skill:{parsed['skill']}"
|
|
329
|
+
|
|
330
|
+
verdict = evaluate_action(
|
|
331
|
+
action_type=action_type,
|
|
332
|
+
agent=agent,
|
|
333
|
+
payload_extra={
|
|
334
|
+
"topic": topic,
|
|
335
|
+
"skill": parsed["skill"],
|
|
336
|
+
"confidence": parsed["confidence"],
|
|
337
|
+
"description": f"Agent {agent}: {action_type} on '{topic}'" if topic else f"Agent {agent}: {action_type}",
|
|
338
|
+
},
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
decision = verdict.get("decision", verdict.get("status", "ALLOW")).upper()
|
|
342
|
+
reason = verdict.get("reason", "")
|
|
343
|
+
|
|
344
|
+
if decision in ("BLOCK", "PENALIZE"):
|
|
345
|
+
label = "BLOCKED" if decision == "BLOCK" else "PENALIZED"
|
|
346
|
+
print(f" [2/4] {label} by NeuroVerse governance")
|
|
347
|
+
print(f" Reason: {reason}")
|
|
348
|
+
if decision == "PENALIZE" and verdict.get("consequence"):
|
|
349
|
+
c = verdict["consequence"]
|
|
350
|
+
print(f" Consequence: {c.get('type', 'unknown')} for {c.get('rounds', 1)} round(s)")
|
|
351
|
+
print(f"\n ScienceClaw execution SKIPPED")
|
|
352
|
+
print(f" {'=' * 50}\n")
|
|
353
|
+
sys.exit(1)
|
|
354
|
+
|
|
355
|
+
elif decision == "REWARD":
|
|
356
|
+
print(f" [2/4] REWARDED — proceeding with boosted priority")
|
|
357
|
+
elif decision == "PAUSE":
|
|
358
|
+
print(f" [2/4] PAUSED — waiting 3s before proceeding")
|
|
359
|
+
time.sleep(3)
|
|
360
|
+
else:
|
|
361
|
+
print(f" [2/4] ALLOWED — proceeding")
|
|
362
|
+
|
|
363
|
+
# ── Level 2: Streaming governance — spawn and monitor ──
|
|
364
|
+
cmd = [SCIENCECLAW_BIN] + parsed["passthrough_args"]
|
|
365
|
+
print(f" [3/4] Executing: {' '.join(cmd)}")
|
|
366
|
+
if ENFORCE_STREAMING:
|
|
367
|
+
print(f" Streaming governance active — each artifact will be evaluated\n")
|
|
368
|
+
else:
|
|
369
|
+
print()
|
|
370
|
+
print(f" {'=' * 50}")
|
|
371
|
+
|
|
372
|
+
try:
|
|
373
|
+
proc = subprocess.Popen(
|
|
374
|
+
cmd,
|
|
375
|
+
stdout=subprocess.PIPE,
|
|
376
|
+
stderr=subprocess.PIPE,
|
|
377
|
+
text=True,
|
|
378
|
+
bufsize=1, # line-buffered
|
|
379
|
+
)
|
|
380
|
+
except FileNotFoundError:
|
|
381
|
+
print(f"\n ERROR: '{SCIENCECLAW_BIN}' not found on PATH")
|
|
382
|
+
print(f" Install ScienceClaw or set SCIENCECLAW_BIN=/path/to/scienceclaw-post")
|
|
383
|
+
sys.exit(127)
|
|
384
|
+
|
|
385
|
+
# Read stderr in background thread to prevent blocking
|
|
386
|
+
stderr_thread = threading.Thread(target=stream_stderr, args=(proc,), daemon=True)
|
|
387
|
+
stderr_thread.start()
|
|
388
|
+
|
|
389
|
+
# Read stdout line by line — pass through to user AND evaluate
|
|
390
|
+
terminated_by_governance = False
|
|
391
|
+
try:
|
|
392
|
+
for line in iter(proc.stdout.readline, ""):
|
|
393
|
+
# Always print the line (user sees ScienceClaw output)
|
|
394
|
+
sys.stdout.write(line)
|
|
395
|
+
sys.stdout.flush()
|
|
396
|
+
|
|
397
|
+
# Evaluate if streaming governance is on
|
|
398
|
+
if ENFORCE_STREAMING and proc.poll() is None:
|
|
399
|
+
govern_output_line(line, agent, proc)
|
|
400
|
+
if proc.poll() is not None:
|
|
401
|
+
terminated_by_governance = True
|
|
402
|
+
break
|
|
403
|
+
except KeyboardInterrupt:
|
|
404
|
+
proc.terminate()
|
|
405
|
+
proc.wait(timeout=5)
|
|
406
|
+
|
|
407
|
+
exit_code = proc.wait()
|
|
408
|
+
|
|
409
|
+
# ── Level 3: Completion report ──
|
|
410
|
+
print(f"\n {'=' * 50}")
|
|
411
|
+
print(f" [4/4] Session complete\n")
|
|
412
|
+
|
|
413
|
+
if terminated_by_governance:
|
|
414
|
+
print(f" Status: HALTED by governance mid-execution")
|
|
415
|
+
elif exit_code == 0:
|
|
416
|
+
print(f" Status: Completed successfully")
|
|
417
|
+
else:
|
|
418
|
+
print(f" Status: Exited with code {exit_code}")
|
|
419
|
+
|
|
420
|
+
print(f" Evaluations: {stats['evaluated']}")
|
|
421
|
+
print(f" Cycles: {stats['cycles_seen']}")
|
|
422
|
+
print(f" Artifacts: {stats['artifacts_seen']}")
|
|
423
|
+
print(f" Allowed: {stats['allowed']}")
|
|
424
|
+
print(f" Blocked: {stats['blocked']}")
|
|
425
|
+
print(f" Penalized: {stats['penalized']}")
|
|
426
|
+
print(f" Rewarded: {stats['rewarded']}")
|
|
427
|
+
|
|
428
|
+
# Report final state to governance server
|
|
429
|
+
try:
|
|
430
|
+
requests.post(
|
|
431
|
+
EVALUATE_ENDPOINT,
|
|
432
|
+
json={
|
|
433
|
+
"actor": agent,
|
|
434
|
+
"action": "investigation_complete",
|
|
435
|
+
"payload": {
|
|
436
|
+
"description": f"Agent {agent} investigation {'halted by governance' if terminated_by_governance else 'completed'} on '{topic}'",
|
|
437
|
+
"exit_code": exit_code,
|
|
438
|
+
"halted_by_governance": terminated_by_governance,
|
|
439
|
+
"stats": stats,
|
|
440
|
+
},
|
|
441
|
+
"world": "science_research",
|
|
442
|
+
},
|
|
443
|
+
timeout=3,
|
|
444
|
+
)
|
|
445
|
+
except Exception:
|
|
446
|
+
pass
|
|
447
|
+
|
|
448
|
+
print(f"\n {'=' * 50}\n")
|
|
449
|
+
sys.exit(1 if terminated_by_governance else exit_code)
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
if __name__ == "__main__":
|
|
453
|
+
run()
|
|
@@ -36,9 +36,12 @@
|
|
|
36
36
|
* const cycleResult = adapter.governCycle(artifacts, reactorMatches)
|
|
37
37
|
*/
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.SCIENCECLAW_CAPABILITIES = void 0;
|
|
40
|
+
exports.mapScienceClawEvent = mapScienceClawEvent;
|
|
39
41
|
exports.createScienceClawAdapter = createScienceClawAdapter;
|
|
40
42
|
exports.generateDemoInvestigation = generateDemoInvestigation;
|
|
41
43
|
const govern_1 = require("../runtime/govern");
|
|
44
|
+
const types_1 = require("../runtime/types");
|
|
42
45
|
const dynamicsGovernance_1 = require("../engine/dynamicsGovernance");
|
|
43
46
|
const science_metrics_1 = require("../engine/metrics/science.metrics");
|
|
44
47
|
// ============================================
|
|
@@ -97,6 +100,31 @@ function artifactToAction(artifact) {
|
|
|
97
100
|
},
|
|
98
101
|
};
|
|
99
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Maps a ScienceClaw artifact to the NV Governance Schema v1.
|
|
105
|
+
* This is the canonical representation that crosses the governance boundary.
|
|
106
|
+
*/
|
|
107
|
+
function mapScienceClawEvent(artifact, cycle) {
|
|
108
|
+
const citationWeight = Math.min(1, artifact.citations / 10);
|
|
109
|
+
const magnitude = artifact.confidence * 0.6 + citationWeight * 0.4;
|
|
110
|
+
return {
|
|
111
|
+
schema: 1,
|
|
112
|
+
source: "scienceclaw",
|
|
113
|
+
cycle,
|
|
114
|
+
agentId: artifact.agentId,
|
|
115
|
+
actionType: (0, types_1.normalizeActionType)(artifact.type),
|
|
116
|
+
description: artifact.description,
|
|
117
|
+
confidence: artifact.confidence,
|
|
118
|
+
magnitude: Number(magnitude.toFixed(3)),
|
|
119
|
+
reproduced: artifact.reproduced,
|
|
120
|
+
references: artifact.citations,
|
|
121
|
+
metadata: {
|
|
122
|
+
artifactId: artifact.id,
|
|
123
|
+
provenance: artifact.provenance,
|
|
124
|
+
skill: artifact.skill,
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
100
128
|
/**
|
|
101
129
|
* Convert artifacts + matches into SwarmAgentReactions for governDynamics().
|
|
102
130
|
*
|
|
@@ -293,6 +321,12 @@ function createScienceClawAdapter(config = {}) {
|
|
|
293
321
|
case "PAUSE":
|
|
294
322
|
actionStats.modified++;
|
|
295
323
|
break; // count pause as modified
|
|
324
|
+
case "REWARD":
|
|
325
|
+
actionStats.approved++;
|
|
326
|
+
break; // rewarded actions still proceed
|
|
327
|
+
case "PENALIZE":
|
|
328
|
+
actionStats.blocked++;
|
|
329
|
+
break; // penalized actions are blocked
|
|
296
330
|
}
|
|
297
331
|
// Build science-specific reason
|
|
298
332
|
const riskFlags = [];
|
|
@@ -309,9 +343,15 @@ function createScienceClawAdapter(config = {}) {
|
|
|
309
343
|
if (verdict.status === "BLOCK") {
|
|
310
344
|
scienceReason = `Artifact blocked: ${artifact.type} from ${artifact.agentId} — ${verdict.reason}`;
|
|
311
345
|
}
|
|
346
|
+
else if (verdict.status === "PENALIZE") {
|
|
347
|
+
scienceReason = `Artifact penalized: ${artifact.type} from ${artifact.agentId} frozen — ${verdict.consequence?.description ?? verdict.reason}`;
|
|
348
|
+
}
|
|
312
349
|
else if (verdict.status === "MODIFY") {
|
|
313
350
|
scienceReason = `Artifact modified: ${artifact.type} impact reduced — ${verdict.reason}`;
|
|
314
351
|
}
|
|
352
|
+
else if (verdict.status === "REWARD") {
|
|
353
|
+
scienceReason = `Artifact rewarded: ${artifact.type} from ${artifact.agentId} — ${verdict.reward?.description ?? verdict.reason}`;
|
|
354
|
+
}
|
|
315
355
|
const result = {
|
|
316
356
|
artifactId: artifact.id,
|
|
317
357
|
verdict,
|
|
@@ -332,11 +372,11 @@ function createScienceClawAdapter(config = {}) {
|
|
|
332
372
|
const blockedArtifactIds = new Set();
|
|
333
373
|
for (const artifact of artifacts) {
|
|
334
374
|
const verdict = governArtifact(artifact);
|
|
335
|
-
if (verdict.verdict.status === "ALLOW") {
|
|
375
|
+
if (verdict.verdict.status === "ALLOW" || verdict.verdict.status === "REWARD") {
|
|
336
376
|
approved++;
|
|
337
377
|
allowedArtifacts.push(artifact);
|
|
338
378
|
}
|
|
339
|
-
else if (verdict.verdict.status === "BLOCK") {
|
|
379
|
+
else if (verdict.verdict.status === "BLOCK" || verdict.verdict.status === "PENALIZE") {
|
|
340
380
|
blocked++;
|
|
341
381
|
blockedArtifactIds.add(artifact.id);
|
|
342
382
|
}
|
|
@@ -748,3 +788,13 @@ function generateDemoInvestigation() {
|
|
|
748
788
|
}
|
|
749
789
|
return { artifactCycles, matchesByCycle };
|
|
750
790
|
}
|
|
791
|
+
exports.SCIENCECLAW_CAPABILITIES = {
|
|
792
|
+
id: "scienceclaw",
|
|
793
|
+
label: "ScienceClaw",
|
|
794
|
+
canControlRounds: false,
|
|
795
|
+
canInterveneLive: true,
|
|
796
|
+
canHalt: true,
|
|
797
|
+
governanceTiming: "live",
|
|
798
|
+
setupInstructions: "ScienceClaw runs independently — NeuroVerse intercepts and governs each artifact cycle in real time.\n\nIf ScienceClaw is on your PATH, the dashboard launches and governs it directly.\n\nOtherwise, run the governance wrapper in a separate terminal:\n python connectors/nv_scienceclaw_wrapper.py --agent MyAgent --topic \"...\"\n\nNeuroVerse evaluates every artifact as it's produced — and can block, modify, or halt the investigation live.",
|
|
799
|
+
connectionCheck: "/api/evaluate",
|
|
800
|
+
};
|