@neuroverseos/nv-sim 0.1.10 → 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.
@@ -1 +1 @@
1
- import{g as d,A as m,e as u,a as v}from"./index-CdghpsS8.js";function p(s){const e=s.governed,i=s.governanceStats,c=e.swarm.rounds.map((a,t)=>{var o;return{round:a.round,reactions:a.reactions.map(n=>({agent:n.stakeholder_id,action:n.reaction,impact:n.impact,verdict:void 0})),interventions:((o=a.emergent_dynamics)==null?void 0:o.filter(n=>n.includes("[BLOCK]")||n.includes("[PAUSE]")||n.includes("CIRCUIT BREAKER")||n.includes("intervention")))??[]}}),r=e.swarm.rounds.flatMap(a=>(a.emergent_dynamics??[]).filter(t=>t.includes("[BLOCK]")||t.includes("[PAUSE]")||t.includes("GATE")||t.includes("Rebalanced")||t.includes("Capped"))),l={avgImpact:e.metrics.avgImpact,collapseProbability:e.metrics.collapseProbability,stabilityScore:e.metrics.stabilityScore,maxVolatility:e.metrics.maxVolatility,peakNegativeSentiment:e.metrics.peakNegativeSentiment};return{scenario:s.scenario,rounds:c,metrics:l,interventions:r,governanceStats:{totalEvaluations:i.totalEvaluations,blocks:i.verdicts.block,pauses:i.verdicts.pause,allows:i.verdicts.allow,rulesFired:i.rulesFired}}}async function y(s,e={}){const i=p(s),c=e.aiEnabled??!1,r=c?e.provider??"deterministic":"deterministic",l=d(r),a=m.find(o=>o.id==="ai_analyst"),t=await u(a,"generate_report",async()=>c&&r!=="deterministic"?l.summarize(i):v(i));return{report:t.result,governance:t,aiUsed:c&&r!=="deterministic",provider:r}}export{p as extractTrace,y as generateGovernedReport};
1
+ import{g as d,A as m,e as u,a as v}from"./index-sT4b_z7w.js";function p(s){const e=s.governed,i=s.governanceStats,c=e.swarm.rounds.map((a,t)=>{var o;return{round:a.round,reactions:a.reactions.map(n=>({agent:n.stakeholder_id,action:n.reaction,impact:n.impact,verdict:void 0})),interventions:((o=a.emergent_dynamics)==null?void 0:o.filter(n=>n.includes("[BLOCK]")||n.includes("[PAUSE]")||n.includes("CIRCUIT BREAKER")||n.includes("intervention")))??[]}}),r=e.swarm.rounds.flatMap(a=>(a.emergent_dynamics??[]).filter(t=>t.includes("[BLOCK]")||t.includes("[PAUSE]")||t.includes("GATE")||t.includes("Rebalanced")||t.includes("Capped"))),l={avgImpact:e.metrics.avgImpact,collapseProbability:e.metrics.collapseProbability,stabilityScore:e.metrics.stabilityScore,maxVolatility:e.metrics.maxVolatility,peakNegativeSentiment:e.metrics.peakNegativeSentiment};return{scenario:s.scenario,rounds:c,metrics:l,interventions:r,governanceStats:{totalEvaluations:i.totalEvaluations,blocks:i.verdicts.block,pauses:i.verdicts.pause,allows:i.verdicts.allow,rulesFired:i.rulesFired}}}async function y(s,e={}){const i=p(s),c=e.aiEnabled??!1,r=c?e.provider??"deterministic":"deterministic",l=d(r),a=m.find(o=>o.id==="ai_analyst"),t=await u(a,"generate_report",async()=>c&&r!=="deterministic"?l.summarize(i):v(i));return{report:t.result,governance:t,aiUsed:c&&r!=="deterministic",provider:r}}export{p as extractTrace,y as generateGovernedReport};
@@ -5,22 +5,23 @@
5
5
  *
6
6
  * Drop-in replacement for `scienceclaw-post` that adds governance.
7
7
  *
8
- * Governance layers:
9
- * Level 1 (entry gate): Evaluates the invocation BEFORE ScienceClaw starts
10
- * Level 2 (streaming): Parses stdout, evaluates each artifact/cycle via /api/evaluate
11
- * Level 3 (completion): Reports final state back to governance server
8
+ * How it works:
9
+ * 1. Entry gate: evaluates the investigation BEFORE ScienceClaw starts
10
+ * 2. Runs scienceclaw-post as a child process (stdout/stderr pass through)
11
+ * 3. Watches ~/.scienceclaw/artifacts/{agent}/store.jsonl for new artifacts
12
+ * 4. Each new artifact line is sent to /api/evaluate for governance
13
+ * 5. Reports final stats when ScienceClaw exits
12
14
  *
13
- * Instead of:
14
- * scienceclaw-post --agent MyAgent --topic "CRISPR risks"
15
+ * ScienceClaw does NOT print JSON to stdout — it writes JSONL to files.
16
+ * So we watch the file, not stdout.
15
17
  *
16
- * Run:
18
+ * Usage:
17
19
  * nv-scienceclaw-post --agent MyAgent --topic "CRISPR risks"
18
20
  *
19
21
  * Environment variables:
20
22
  * NEUROVERSE_URL — Governance server URL (default: http://localhost:3456)
21
23
  * SCIENCECLAW_BIN — Path to scienceclaw-post binary (default: scienceclaw-post)
22
24
  * NV_VERBOSE — Set to "1" for verbose output
23
- * NV_ENFORCE_STREAMING — Set to "0" to disable streaming governance (entry gate only)
24
25
  *
25
26
  * Zero edits to ScienceClaw required.
26
27
  */
@@ -29,6 +30,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
29
30
  };
30
31
  Object.defineProperty(exports, "__esModule", { value: true });
31
32
  const child_process_1 = require("child_process");
33
+ const fs_1 = require("fs");
34
+ const os_1 = require("os");
35
+ const path_1 = require("path");
32
36
  const http_1 = __importDefault(require("http"));
33
37
  const https_1 = __importDefault(require("https"));
34
38
  // ============================================
@@ -37,7 +41,6 @@ const https_1 = __importDefault(require("https"));
37
41
  const NEUROVERSE_URL = process.env.NEUROVERSE_URL ?? "http://localhost:3456";
38
42
  const SCIENCECLAW_BIN = process.env.SCIENCECLAW_BIN ?? "scienceclaw-post";
39
43
  const VERBOSE = process.env.NV_VERBOSE === "1";
40
- const ENFORCE_STREAMING = process.env.NV_ENFORCE_STREAMING !== "0";
41
44
  // ============================================
42
45
  // STATS
43
46
  // ============================================
@@ -48,7 +51,6 @@ const stats = {
48
51
  penalized: 0,
49
52
  rewarded: 0,
50
53
  modified: 0,
51
- cyclesSeen: 0,
52
54
  artifactsSeen: 0,
53
55
  };
54
56
  function parseArgs(argv) {
@@ -57,9 +59,7 @@ function parseArgs(argv) {
57
59
  agent: "unknown",
58
60
  topic: "",
59
61
  skill: null,
60
- confidence: null,
61
62
  passthroughArgs: [],
62
- noStream: false,
63
63
  };
64
64
  const nvArgIndices = new Set();
65
65
  let i = 0;
@@ -77,10 +77,6 @@ function parseArgs(argv) {
77
77
  parsed.skill = args[++i] ?? null;
78
78
  i++;
79
79
  break;
80
- case "--confidence":
81
- parsed.confidence = parseFloat(args[++i] ?? "0.5");
82
- i++;
83
- break;
84
80
  case "--nv-url":
85
81
  nvArgIndices.add(i);
86
82
  nvArgIndices.add(i + 1);
@@ -90,11 +86,6 @@ function parseArgs(argv) {
90
86
  nvArgIndices.add(i);
91
87
  i++;
92
88
  break;
93
- case "--nv-no-stream":
94
- nvArgIndices.add(i);
95
- parsed.noStream = true;
96
- i++;
97
- break;
98
89
  default:
99
90
  i++;
100
91
  }
@@ -103,7 +94,7 @@ function parseArgs(argv) {
103
94
  return parsed;
104
95
  }
105
96
  // ============================================
106
- // GOVERNANCE EVALUATION (zero dependencies)
97
+ // GOVERNANCE EVALUATION
107
98
  // ============================================
108
99
  function evaluate(actionType, agent, extra) {
109
100
  return new Promise((resolve) => {
@@ -111,7 +102,7 @@ function evaluate(actionType, agent, extra) {
111
102
  actor: agent,
112
103
  action: actionType,
113
104
  payload: {
114
- description: `Agent ${agent} executing ${actionType}`,
105
+ description: extra?.description ?? `Agent ${agent} executing ${actionType}`,
115
106
  ...extra,
116
107
  },
117
108
  world: "science_research",
@@ -158,7 +149,7 @@ function evaluate(actionType, agent, extra) {
158
149
  req.on("error", () => {
159
150
  if (stats.evaluated === 0) {
160
151
  console.log(` [NV] WARNING: NeuroVerse server not reachable at ${NEUROVERSE_URL}`);
161
- console.log(` [NV] Start it with: nv-sim serve`);
152
+ console.log(` [NV] Start it with: npx tsx src/server/index.ts`);
162
153
  console.log(` [NV] Falling back to ALLOW (ungoverned mode)\n`);
163
154
  }
164
155
  resolve({ decision: "ALLOW", reason: "Governance server unavailable" });
@@ -172,79 +163,111 @@ function evaluate(actionType, agent, extra) {
172
163
  });
173
164
  }
174
165
  // ============================================
175
- // STREAMING GOVERNANCE — parse + evaluate
176
- //
177
- // Schema-gated: only strict JSON lines are governed.
178
- // Non-JSON output is passed through without evaluation (by design).
179
- // No regex or heuristic parsing — safety and determinism over flexibility.
166
+ // JSONL FILE WATCHER
180
167
  // ============================================
181
- async function governLine(line, agent, killFn) {
182
- const trimmed = line.trim();
183
- if (!trimmed)
184
- return false;
185
- if (!trimmed.startsWith("{")) {
186
- if (VERBOSE)
187
- console.log(` [NV] [ungoverned: not_json] ${trimmed.slice(0, 120)}`);
188
- return false;
189
- }
190
- let data;
168
+ /**
169
+ * Watch a JSONL file for new lines appended by ScienceClaw.
170
+ * Each new line is parsed and sent to governance.
171
+ *
172
+ * ScienceClaw writes artifacts to:
173
+ * ~/.scienceclaw/artifacts/{agent}/store.jsonl
174
+ *
175
+ * Each line is a JSON artifact with fields:
176
+ * artifact_id, artifact_type, producer_agent, skill_used,
177
+ * payload, investigation_id, content_hash, parent_artifact_ids,
178
+ * result_quality, needs, timestamp
179
+ */
180
+ function watchArtifactFile(filePath, agent) {
181
+ let fileSize = 0;
182
+ // Get initial file size (skip existing content)
191
183
  try {
192
- data = JSON.parse(trimmed);
193
- }
194
- catch {
195
- if (VERBOSE)
196
- console.log(` [NV] [ungoverned: invalid_json] ${trimmed.slice(0, 120)}`);
197
- return false;
184
+ if ((0, fs_1.existsSync)(filePath)) {
185
+ fileSize = (0, fs_1.statSync)(filePath).size;
186
+ }
198
187
  }
199
- const cycle = data.cycle ?? data.step ?? data.round;
200
- const artifacts = data.artifacts ?? data.agent_actions ?? [];
201
- if (cycle == null) {
202
- if (VERBOSE)
203
- console.log(` [NV] [ungoverned: missing_cycle] ${trimmed.slice(0, 120)}`);
204
- return false;
188
+ catch { /* file doesn't exist yet */ }
189
+ if (VERBOSE)
190
+ console.log(` [NV] Watching: ${filePath}`);
191
+ if (VERBOSE)
192
+ console.log(` [NV] Starting from byte offset: ${fileSize}`);
193
+ // Poll for changes (fs.watch is unreliable across platforms for appends)
194
+ const interval = setInterval(() => {
195
+ try {
196
+ if (!(0, fs_1.existsSync)(filePath))
197
+ return;
198
+ const currentSize = (0, fs_1.statSync)(filePath).size;
199
+ if (currentSize <= fileSize)
200
+ return;
201
+ // Read only the new bytes
202
+ const fd = (0, fs_1.readFileSync)(filePath, "utf-8");
203
+ const newContent = fd.slice(fileSize);
204
+ fileSize = currentSize;
205
+ // Parse each new line
206
+ const lines = newContent.split("\n").filter((l) => l.trim());
207
+ for (const line of lines) {
208
+ try {
209
+ const artifact = JSON.parse(line);
210
+ governArtifact(artifact, agent);
211
+ }
212
+ catch {
213
+ if (VERBOSE)
214
+ console.log(` [NV] [skip] non-JSON line in artifact file`);
215
+ }
216
+ }
217
+ }
218
+ catch (err) {
219
+ if (VERBOSE)
220
+ console.log(` [NV] [watch error] ${err}`);
221
+ }
222
+ }, 500); // Check every 500ms
223
+ return {
224
+ stop: () => clearInterval(interval),
225
+ };
226
+ }
227
+ /**
228
+ * Send a ScienceClaw artifact to governance.
229
+ */
230
+ async function governArtifact(artifact, agent) {
231
+ stats.artifactsSeen++;
232
+ const artifactType = artifact.artifact_type ?? artifact.type ?? "unknown";
233
+ const skillUsed = artifact.skill_used ?? "unknown";
234
+ const quality = artifact.result_quality ?? "ok";
235
+ const producer = artifact.producer_agent ?? agent;
236
+ const description = artifact.payload?.title
237
+ ?? artifact.payload?.query
238
+ ?? `${artifactType} via ${skillUsed}`;
239
+ const verdict = await evaluate(`publish_${artifactType}`, producer, {
240
+ description,
241
+ artifact_type: artifactType,
242
+ skill_used: skillUsed,
243
+ result_quality: quality,
244
+ artifact_id: artifact.artifact_id,
245
+ investigation_id: artifact.investigation_id,
246
+ parent_artifacts: artifact.parent_artifact_ids?.length ?? 0,
247
+ has_needs: Array.isArray(artifact.needs) && artifact.needs.length > 0,
248
+ });
249
+ const decision = (verdict.decision ?? verdict.status ?? "ALLOW").toUpperCase();
250
+ if (decision === "BLOCK" || decision === "PENALIZE") {
251
+ console.log(`\n [NV] BLOCKED: ${artifactType} by ${producer}`);
252
+ console.log(` Skill: ${skillUsed}`);
253
+ console.log(` Reason: ${verdict.reason ?? "policy violation"}`);
254
+ if (verdict.consequence) {
255
+ console.log(` Consequence: ${verdict.consequence.description ?? verdict.consequence.type}`);
256
+ }
205
257
  }
206
- if (artifacts.length === 0) {
258
+ else if (decision === "ALLOW") {
207
259
  if (VERBOSE)
208
- console.log(` [NV] [ungoverned: no_artifacts] cycle=${cycle}`);
209
- return false;
260
+ console.log(` [NV] ALLOWED: ${artifactType} by ${producer} (${skillUsed})`);
210
261
  }
211
- // ── Schema-valid: cycle + artifacts present ──
212
- stats.cyclesSeen++;
213
- for (const art of artifacts) {
214
- stats.artifactsSeen++;
215
- const artAgent = art.agent_id ?? art.id ?? art.agent ?? agent;
216
- const artType = art.type ?? art.action ?? "publish";
217
- const verdict = await evaluate(`publish_${artType}`, artAgent, {
218
- description: art.description ?? `${artType} artifact`,
219
- artifact_type: artType,
220
- confidence: art.confidence,
221
- reproduced: art.reproduced ?? false,
222
- citations: art.citations ?? 0,
223
- cycle,
224
- });
225
- const decision = (verdict.decision ?? verdict.status ?? "ALLOW").toUpperCase();
226
- if ((decision === "BLOCK" || decision === "PENALIZE") && ENFORCE_STREAMING) {
227
- console.log(`\n [NV] GOVERNANCE HALT at cycle ${cycle}`);
228
- console.log(` Artifact: ${art.description ?? artType}`);
229
- console.log(` Agent: ${artAgent}`);
230
- console.log(` Decision: ${decision}`);
231
- console.log(` Reason: ${verdict.reason ?? "policy violation"}`);
232
- if (decision === "PENALIZE" && verdict.consequence) {
233
- console.log(` Consequence: ${verdict.consequence.type} for ${verdict.consequence.rounds ?? 1} round(s)`);
234
- }
235
- console.log(` Terminating ScienceClaw process...\n`);
236
- killFn();
237
- return true; // halted
238
- }
262
+ else if (decision === "REWARD") {
263
+ console.log(` [NV] REWARDED: ${artifactType} by ${producer} — ${verdict.reward?.description ?? ""}`);
239
264
  }
240
- return false;
241
265
  }
242
266
  // ============================================
243
267
  // MAIN
244
268
  // ============================================
245
269
  async function main() {
246
270
  const parsed = parseArgs(process.argv);
247
- const streamingEnabled = ENFORCE_STREAMING && !parsed.noStream;
248
271
  console.log(`\n NeuroVerse ScienceClaw Connector`);
249
272
  console.log(` ${"=".repeat(50)}`);
250
273
  console.log(` Agent: ${parsed.agent}`);
@@ -252,122 +275,86 @@ async function main() {
252
275
  if (parsed.skill)
253
276
  console.log(` Skill: ${parsed.skill}`);
254
277
  console.log(` Server: ${NEUROVERSE_URL}`);
255
- console.log(` Streaming: ${streamingEnabled ? "ON — each artifact evaluated" : "OFF — entry gate only"}`);
256
278
  console.log(` ${"=".repeat(50)}\n`);
257
279
  // ── Level 1: Entry gate ──
258
- console.log(` [1/4] Evaluating launch governance...`);
280
+ console.log(` [1/3] Evaluating launch governance...`);
259
281
  const actionType = parsed.skill ? `execute_skill:${parsed.skill}` : "run_investigation";
260
282
  const verdict = await evaluate(actionType, parsed.agent, {
261
283
  topic: parsed.topic,
262
284
  skill: parsed.skill,
263
- confidence: parsed.confidence,
264
285
  description: `Agent ${parsed.agent}: ${actionType}${parsed.topic ? ` on '${parsed.topic}'` : ""}`,
265
286
  });
266
287
  const decision = (verdict.decision ?? verdict.status ?? "ALLOW").toUpperCase();
267
288
  if (decision === "BLOCK" || decision === "PENALIZE") {
268
289
  const label = decision === "BLOCK" ? "BLOCKED" : "PENALIZED";
269
- console.log(` [2/4] ${label} by NeuroVerse governance`);
290
+ console.log(` ${label} by NeuroVerse governance`);
270
291
  console.log(` Reason: ${verdict.reason ?? ""}`);
271
- if (decision === "PENALIZE" && verdict.consequence) {
272
- console.log(` Consequence: ${verdict.consequence.type} for ${verdict.consequence.rounds ?? 1} round(s)`);
273
- }
274
292
  console.log(`\n ScienceClaw execution SKIPPED`);
275
293
  console.log(` ${"=".repeat(50)}\n`);
276
294
  process.exit(1);
277
295
  }
278
- if (decision === "REWARD") {
279
- console.log(` [2/4] REWARDED proceeding with boosted priority`);
280
- }
281
- else if (decision === "PAUSE") {
282
- console.log(` [2/4] PAUSED waiting 3s before proceeding`);
283
- await new Promise((r) => setTimeout(r, 3000));
284
- }
285
- else {
286
- console.log(` [2/4] ALLOWED — proceeding`);
287
- }
288
- // ── Level 2: Streaming governance ──
296
+ console.log(` ${decision} proceeding\n`);
297
+ // ── Level 2: Run ScienceClaw + watch artifact file ──
298
+ const artifactDir = (0, path_1.join)((0, os_1.homedir)(), ".scienceclaw", "artifacts", parsed.agent);
299
+ const storePath = (0, path_1.join)(artifactDir, "store.jsonl");
300
+ const globalPath = (0, path_1.join)((0, os_1.homedir)(), ".scienceclaw", "artifacts", "global_index.jsonl");
301
+ // Watch both the agent store and global index
302
+ console.log(` [2/3] Running ScienceClaw + watching artifacts...`);
303
+ console.log(` Agent store: ${storePath}`);
304
+ console.log(` Global index: ${globalPath}\n`);
305
+ const storeWatcher = watchArtifactFile(storePath, parsed.agent);
306
+ const globalWatcher = watchArtifactFile(globalPath, parsed.agent);
289
307
  const cmd = SCIENCECLAW_BIN;
290
308
  const cmdArgs = parsed.passthroughArgs;
291
- console.log(` [3/4] Executing: ${cmd} ${cmdArgs.join(" ")}`);
292
- if (streamingEnabled) {
293
- console.log(` Streaming governance active — each artifact will be evaluated\n`);
294
- }
295
- else {
296
- console.log();
297
- }
298
- console.log(` ${"=".repeat(50)}`);
309
+ console.log(` Executing: ${cmd} ${cmdArgs.join(" ")}`);
310
+ console.log(` ${"=".repeat(50)}\n`);
299
311
  const child = (0, child_process_1.spawn)(cmd, cmdArgs, {
300
312
  stdio: ["inherit", "pipe", "pipe"],
301
313
  shell: process.platform === "win32",
302
314
  });
303
- let haltedByGovernance = false;
304
- const killChild = () => {
305
- haltedByGovernance = true;
306
- try {
307
- child.kill();
308
- }
309
- catch { /* ignore */ }
310
- };
311
- // Pipe stderr through
315
+ // Pass through stdout and stderr so user sees ScienceClaw's normal output
316
+ child.stdout?.on("data", (chunk) => {
317
+ process.stdout.write(chunk);
318
+ });
312
319
  child.stderr?.on("data", (chunk) => {
313
320
  process.stderr.write(chunk);
314
321
  });
315
- // Pipe stdout through AND evaluate each line
316
- let lineBuffer = "";
317
- child.stdout?.on("data", (chunk) => {
318
- const text = chunk.toString();
319
- process.stdout.write(text); // always pass through
320
- if (!streamingEnabled)
321
- return;
322
- lineBuffer += text;
323
- const lines = lineBuffer.split("\n");
324
- lineBuffer = lines.pop() ?? ""; // keep incomplete line
325
- for (const line of lines) {
326
- if (line.trim() && !haltedByGovernance) {
327
- // Fire and forget — evaluate in background
328
- governLine(line, parsed.agent, killChild);
329
- }
330
- }
331
- });
332
322
  child.on("error", (err) => {
323
+ storeWatcher.stop();
324
+ globalWatcher.stop();
333
325
  if (err.code === "ENOENT") {
334
326
  console.log(`\n ERROR: '${cmd}' not found on PATH`);
335
- console.log(` Install ScienceClaw or set SCIENCECLAW_BIN=/path/to/scienceclaw-post`);
327
+ console.log(` Install ScienceClaw: git clone https://github.com/lamm-mit/scienceclaw && cd scienceclaw && pip install -e .`);
328
+ console.log(` Or set SCIENCECLAW_BIN=/path/to/scienceclaw-post`);
336
329
  }
337
330
  else {
338
331
  console.log(`\n ERROR: Failed to start ScienceClaw: ${err.message}`);
339
332
  }
340
333
  process.exit(127);
341
334
  });
342
- child.on("close", (code) => {
335
+ child.on("close", async (code) => {
336
+ // Wait a moment for final file writes to flush
337
+ await new Promise((r) => setTimeout(r, 1000));
338
+ storeWatcher.stop();
339
+ globalWatcher.stop();
343
340
  // ── Level 3: Completion report ──
344
341
  console.log(`\n ${"=".repeat(50)}`);
345
- console.log(` [4/4] Session complete\n`);
346
- if (haltedByGovernance) {
347
- console.log(` Status: HALTED by governance mid-execution`);
348
- }
349
- else if (code === 0) {
350
- console.log(` Status: Completed successfully`);
351
- }
352
- else {
353
- console.log(` Status: Exited with code ${code}`);
354
- }
355
- console.log(` Evaluations: ${stats.evaluated}`);
356
- console.log(` Cycles: ${stats.cyclesSeen}`);
342
+ console.log(` [3/3] Session complete\n`);
343
+ console.log(` Status: ${code === 0 ? "Completed successfully" : `Exited with code ${code}`}`);
357
344
  console.log(` Artifacts: ${stats.artifactsSeen}`);
345
+ console.log(` Evaluations: ${stats.evaluated}`);
358
346
  console.log(` Allowed: ${stats.allowed}`);
359
347
  console.log(` Blocked: ${stats.blocked}`);
360
348
  console.log(` Penalized: ${stats.penalized}`);
361
349
  console.log(` Rewarded: ${stats.rewarded}`);
362
- // Report to governance server (fire and forget)
350
+ // Report to governance server
363
351
  evaluate("investigation_complete", parsed.agent, {
364
- description: `Agent ${parsed.agent} investigation ${haltedByGovernance ? "halted by governance" : "completed"}`,
352
+ description: `Agent ${parsed.agent} investigation completed`,
365
353
  exit_code: code,
366
- halted_by_governance: haltedByGovernance,
367
354
  stats,
368
355
  }).catch(() => { });
369
356
  console.log(`\n ${"=".repeat(50)}\n`);
370
- process.exit(haltedByGovernance ? 1 : (code ?? 0));
357
+ process.exit(code ?? 0);
371
358
  });
372
359
  }
373
360
  main().catch((err) => {