@heretek-ai/epistemic-swarm 0.1.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/.claude-plugin/marketplace.json +14 -0
- package/.claude-plugin/plugin.json +58 -0
- package/LICENSE +126 -0
- package/README.md +130 -0
- package/bin/cli.js +119 -0
- package/config/claude-settings-patch.json +10 -0
- package/config/docker-compose.infra.yml +23 -0
- package/config/mcp-research-servers.json +26 -0
- package/config/searxng_mcp.py +133 -0
- package/install.sh +102 -0
- package/package.json +52 -0
- package/prompts/agent_alpha_thesis.md +70 -0
- package/prompts/agent_beta_antithesis.md +78 -0
- package/prompts/base_epistemic_system.md +50 -0
- package/prompts/epistemic_auditor.md +74 -0
- package/prompts/orchestrator.md +70 -0
- package/runner/__init__.py +0 -0
- package/runner/__pycache__/__init__.cpython-314.pyc +0 -0
- package/runner/__pycache__/auditor_engine.cpython-314.pyc +0 -0
- package/runner/__pycache__/research_swarm.cpython-314.pyc +0 -0
- package/runner/__pycache__/state_machine.cpython-314.pyc +0 -0
- package/runner/auditor_engine.py +222 -0
- package/runner/research_swarm.py +337 -0
- package/runner/state_machine.py +192 -0
- package/runner/tests/__pycache__/test_swarm.cpython-314.pyc +0 -0
- package/runner/tests/test_swarm.py +178 -0
- package/skills/grilling/SKILL.md +48 -0
- package/skills/grilling/__init__.py +0 -0
- package/skills/grilling/socratic_tree.py +148 -0
- package/skills/research-cache/SKILL.md +36 -0
- package/skills/research-cache/__init__.py +0 -0
- package/skills/research-cache/__pycache__/__init__.cpython-314.pyc +0 -0
- package/skills/research-cache/__pycache__/hasher.cpython-314.pyc +0 -0
- package/skills/research-cache/hasher.py +195 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Algorithmic Epistemic Auditor Engine for Epistemic Swarm.
|
|
4
|
+
Verifies quote authenticity against content-addressed source cache,
|
|
5
|
+
calculates divergence metrics, prunes ungrounded claims, and generates synthesis reports.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
import json
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
from typing import Dict, Any, List, Tuple, Optional
|
|
13
|
+
|
|
14
|
+
# Import SourceHasher from skills/research-cache
|
|
15
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
16
|
+
from skills.research_cache.hasher import SourceHasher
|
|
17
|
+
from runner.state_machine import ResearchStateMachine, ScopeStatus
|
|
18
|
+
|
|
19
|
+
class EpistemicAuditorEngine:
|
|
20
|
+
def __init__(self, base_dir: Optional[Path] = None):
|
|
21
|
+
self.base_dir = base_dir or Path(".research")
|
|
22
|
+
self.hasher = SourceHasher(base_dir=self.base_dir)
|
|
23
|
+
self.state_machine = ResearchStateMachine(base_dir=self.base_dir)
|
|
24
|
+
|
|
25
|
+
def audit_scope(self, scope_id: str) -> Dict[str, Any]:
|
|
26
|
+
"""Runs the audit pipeline on a scope with ready dossiers."""
|
|
27
|
+
scope_dir = self.state_machine.get_scope_dir(scope_id)
|
|
28
|
+
alpha_file = scope_dir / "alpha_dossier.json"
|
|
29
|
+
beta_file = scope_dir / "beta_dossier.json"
|
|
30
|
+
|
|
31
|
+
if not alpha_file.exists() or not beta_file.exists():
|
|
32
|
+
raise FileNotFoundError(f"Both dossiers must exist to audit {scope_id}")
|
|
33
|
+
|
|
34
|
+
with open(alpha_file, "r", encoding="utf-8") as f:
|
|
35
|
+
alpha_dossier = json.load(f)
|
|
36
|
+
with open(beta_file, "r", encoding="utf-8") as f:
|
|
37
|
+
beta_dossier = json.load(f)
|
|
38
|
+
|
|
39
|
+
self.state_machine.update_scope_status(scope_id, ScopeStatus.AUDITING)
|
|
40
|
+
|
|
41
|
+
# 1. Audit Alpha Claims
|
|
42
|
+
alpha_results, alpha_verified, alpha_rejected = self._verify_claims(
|
|
43
|
+
alpha_dossier.get("affirmative_claims", [])
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# 2. Audit Beta Claims
|
|
47
|
+
beta_results, beta_verified, beta_rejected = self._verify_claims(
|
|
48
|
+
beta_dossier.get("falsification_claims", [])
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
total_verified = alpha_verified + beta_verified
|
|
52
|
+
total_rejected = alpha_rejected + beta_rejected
|
|
53
|
+
|
|
54
|
+
# Negative knowledge counts
|
|
55
|
+
neg_knowledge_alpha = len(alpha_dossier.get("negative_knowledge", []))
|
|
56
|
+
neg_knowledge_beta = len(beta_dossier.get("negative_knowledge", []))
|
|
57
|
+
total_neg_knowledge = neg_knowledge_alpha + neg_knowledge_beta
|
|
58
|
+
|
|
59
|
+
total_inferred = len(alpha_dossier.get("inferred_implications", []))
|
|
60
|
+
total_hypotheses = len(beta_dossier.get("hypotheses", []))
|
|
61
|
+
|
|
62
|
+
# 3. Calculate Epistemic Score
|
|
63
|
+
total_assertions = max(1, total_verified + total_inferred + total_hypotheses + total_rejected)
|
|
64
|
+
raw_score = (1.0 * total_verified + 0.5 * total_neg_knowledge - 2.5 * total_rejected) / total_assertions
|
|
65
|
+
epistemic_score = max(0.0, min(1.0, round(raw_score, 3)))
|
|
66
|
+
|
|
67
|
+
# 4. Calculate Divergence Score
|
|
68
|
+
divergence_score, divergence_matrix = self._compute_divergence(alpha_dossier, beta_dossier)
|
|
69
|
+
|
|
70
|
+
# 5. Build Audit Report
|
|
71
|
+
audit_report = {
|
|
72
|
+
"auditor": "Epistemic Auditor Engine v1.0",
|
|
73
|
+
"scope_id": scope_id,
|
|
74
|
+
"audited_at": datetime.now(timezone.utc).isoformat(),
|
|
75
|
+
"summary": {
|
|
76
|
+
"total_claims_audited": len(alpha_results) + len(beta_results),
|
|
77
|
+
"verified_passed": total_verified,
|
|
78
|
+
"unverified_rejected": total_rejected,
|
|
79
|
+
"negative_knowledge_count": total_neg_knowledge,
|
|
80
|
+
"epistemic_score": epistemic_score,
|
|
81
|
+
"divergence_score": divergence_score,
|
|
82
|
+
"verdict": "CERTIFIED" if epistemic_score >= 0.65 else "WARNING_LOW_GROUNDING"
|
|
83
|
+
},
|
|
84
|
+
"alpha_claims_audit": alpha_results,
|
|
85
|
+
"beta_claims_audit": beta_results,
|
|
86
|
+
"divergence_matrix": divergence_matrix
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# Save audit_report.json
|
|
90
|
+
with open(scope_dir / "audit_report.json", "w", encoding="utf-8") as f:
|
|
91
|
+
json.dump(audit_report, f, indent=2)
|
|
92
|
+
|
|
93
|
+
# 6. Generate Synthesis Markdown
|
|
94
|
+
synthesis_md = self._generate_synthesis_markdown(
|
|
95
|
+
scope_id, alpha_dossier, beta_dossier, audit_report
|
|
96
|
+
)
|
|
97
|
+
with open(scope_dir / "scope_synthesis.md", "w", encoding="utf-8") as f:
|
|
98
|
+
f.write(synthesis_md)
|
|
99
|
+
|
|
100
|
+
# Mark scope completed
|
|
101
|
+
sm = self.state_machine.load_scope_manifest(scope_id)
|
|
102
|
+
sm["status"] = ScopeStatus.COMPLETE.value
|
|
103
|
+
sm["audit_completed"] = True
|
|
104
|
+
self.state_machine.save_scope_manifest(scope_id, sm)
|
|
105
|
+
|
|
106
|
+
return audit_report
|
|
107
|
+
|
|
108
|
+
def _verify_claims(self, claims: List[Dict[str, Any]]) -> Tuple[List[Dict[str, Any]], int, int]:
|
|
109
|
+
audited_claims = []
|
|
110
|
+
verified_count = 0
|
|
111
|
+
rejected_count = 0
|
|
112
|
+
|
|
113
|
+
for claim in claims:
|
|
114
|
+
cid = claim.get("claim_id", "UNKNOWN")
|
|
115
|
+
shash = claim.get("source_hash", "")
|
|
116
|
+
quote = claim.get("verbatim_quote", "")
|
|
117
|
+
statement = claim.get("statement", "")
|
|
118
|
+
|
|
119
|
+
if not shash or not quote:
|
|
120
|
+
audited_claims.append({
|
|
121
|
+
"claim_id": cid,
|
|
122
|
+
"statement": statement,
|
|
123
|
+
"original_tag": claim.get("tag", "VERIFIED"),
|
|
124
|
+
"audited_tag": "UNVERIFIED_REJECTED",
|
|
125
|
+
"reason": "Missing source_hash or verbatim_quote",
|
|
126
|
+
"confidence": 0.0
|
|
127
|
+
})
|
|
128
|
+
rejected_count += 1
|
|
129
|
+
continue
|
|
130
|
+
|
|
131
|
+
passed, conf, msg = self.hasher.verify_quote(shash, quote)
|
|
132
|
+
if passed:
|
|
133
|
+
audited_claims.append({
|
|
134
|
+
"claim_id": cid,
|
|
135
|
+
"statement": statement,
|
|
136
|
+
"original_tag": "VERIFIED",
|
|
137
|
+
"audited_tag": "VERIFIED",
|
|
138
|
+
"source_hash": shash,
|
|
139
|
+
"confidence": conf,
|
|
140
|
+
"verification_message": msg
|
|
141
|
+
})
|
|
142
|
+
verified_count += 1
|
|
143
|
+
else:
|
|
144
|
+
audited_claims.append({
|
|
145
|
+
"claim_id": cid,
|
|
146
|
+
"statement": statement,
|
|
147
|
+
"original_tag": "VERIFIED",
|
|
148
|
+
"audited_tag": "UNVERIFIED_REJECTED",
|
|
149
|
+
"source_hash": shash,
|
|
150
|
+
"rejected_quote": quote,
|
|
151
|
+
"reason": msg,
|
|
152
|
+
"confidence": conf
|
|
153
|
+
})
|
|
154
|
+
rejected_count += 1
|
|
155
|
+
|
|
156
|
+
return audited_claims, verified_count, rejected_count
|
|
157
|
+
|
|
158
|
+
def _compute_divergence(self, alpha_dossier: Dict[str, Any],
|
|
159
|
+
beta_dossier: Dict[str, Any]) -> Tuple[float, List[Dict[str, Any]]]:
|
|
160
|
+
alpha_claims = alpha_dossier.get("affirmative_claims", [])
|
|
161
|
+
beta_claims = beta_dossier.get("falsification_claims", [])
|
|
162
|
+
critiques = beta_dossier.get("methodological_critiques", [])
|
|
163
|
+
|
|
164
|
+
matrix = []
|
|
165
|
+
contradictions = 0
|
|
166
|
+
|
|
167
|
+
for crit in critiques:
|
|
168
|
+
target = crit.get("target_assertion", "")
|
|
169
|
+
matrix.append({
|
|
170
|
+
"tension_type": "METHODOLOGICAL_CHALLENGE",
|
|
171
|
+
"proponent_claim": target,
|
|
172
|
+
"adversary_critique": crit.get("critique", ""),
|
|
173
|
+
"counter_evidence_hash": crit.get("evidence_hash", "NONE")
|
|
174
|
+
})
|
|
175
|
+
contradictions += 1
|
|
176
|
+
|
|
177
|
+
# Calculate divergence
|
|
178
|
+
total_claims = max(1, len(alpha_claims) + len(beta_claims))
|
|
179
|
+
divergence = round(min(1.0, (contradictions * 2) / total_claims), 2)
|
|
180
|
+
return divergence, matrix
|
|
181
|
+
|
|
182
|
+
def _generate_synthesis_markdown(self, scope_id: str, alpha: Dict[str, Any],
|
|
183
|
+
beta: Dict[str, Any], audit: Dict[str, Any]) -> str:
|
|
184
|
+
summary = audit["summary"]
|
|
185
|
+
md = []
|
|
186
|
+
md.append(f"# Epistemic Synthesis: Scope {scope_id}\n")
|
|
187
|
+
md.append(f"**Audit Verdict**: `{summary['verdict']}` | **Epistemic Score**: `{summary['epistemic_score']}/1.0` | **Divergence Index**: `{summary['divergence_score']}`\n")
|
|
188
|
+
|
|
189
|
+
md.append("## 1. Verified Empirical Grounding")
|
|
190
|
+
for claim in audit["alpha_claims_audit"]:
|
|
191
|
+
if claim["audited_tag"] == "VERIFIED":
|
|
192
|
+
md.append(f"- `[VERIFIED: {claim['source_hash'][:8]}]` {claim['statement']}")
|
|
193
|
+
for claim in audit["beta_claims_audit"]:
|
|
194
|
+
if claim["audited_tag"] == "VERIFIED":
|
|
195
|
+
md.append(f"- `[VERIFIED: {claim['source_hash'][:8]}]` (Counter-Evidence) {claim['statement']}")
|
|
196
|
+
|
|
197
|
+
md.append("\n## 2. Dialectic Tensions & Falsification Audit")
|
|
198
|
+
if audit["divergence_matrix"]:
|
|
199
|
+
for item in audit["divergence_matrix"]:
|
|
200
|
+
md.append(f"### Tension: {item['tension_type']}")
|
|
201
|
+
md.append(f"- **Thesis Assertion**: {item['proponent_claim']}")
|
|
202
|
+
md.append(f"- **Adversarial Critique**: {item['adversary_critique']}")
|
|
203
|
+
else:
|
|
204
|
+
md.append("No active contradictions identified between primary literature and red-team findings.")
|
|
205
|
+
|
|
206
|
+
md.append("\n## 3. Rejected & Unverified Assertions")
|
|
207
|
+
rejected = [c for c in audit["alpha_claims_audit"] + audit["beta_claims_audit"] if c["audited_tag"] == "UNVERIFIED_REJECTED"]
|
|
208
|
+
if rejected:
|
|
209
|
+
for r in rejected:
|
|
210
|
+
md.append(f"- ā ļø **PURGED**: \"{r['statement']}\" ā *Reason: {r['reason']}*")
|
|
211
|
+
else:
|
|
212
|
+
md.append("Zero claims rejected. 100% of cited assertions verified against source cache.")
|
|
213
|
+
|
|
214
|
+
md.append("\n## 4. Negative Knowledge Catalog")
|
|
215
|
+
all_neg = alpha.get("negative_knowledge", []) + beta.get("negative_knowledge", [])
|
|
216
|
+
if all_neg:
|
|
217
|
+
for n in all_neg:
|
|
218
|
+
md.append(f"- `[NEGATIVE_KNOWLEDGE: {n['query']}]` {n['finding']}")
|
|
219
|
+
else:
|
|
220
|
+
md.append("No negative knowledge declarations logged.")
|
|
221
|
+
|
|
222
|
+
return "\n".join(md)
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Epistemic Swarm: Dialectic Multi-Agent Research Runner.
|
|
4
|
+
Executes parallel Claude Code sub-processes (claude -p) for Proponent and Adversary agents,
|
|
5
|
+
monitors filesystem IPC scratchpads, and invokes the Epistemic Auditor.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
import json
|
|
11
|
+
import shutil
|
|
12
|
+
import argparse
|
|
13
|
+
import subprocess
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from datetime import datetime, timezone
|
|
16
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
17
|
+
from typing import Dict, Any, List, Optional
|
|
18
|
+
|
|
19
|
+
# Ensure project root is in sys.path
|
|
20
|
+
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
|
21
|
+
sys.path.insert(0, str(PROJECT_ROOT))
|
|
22
|
+
|
|
23
|
+
from runner.state_machine import ResearchStateMachine, SessionStatus, ScopeStatus
|
|
24
|
+
from runner.auditor_engine import EpistemicAuditorEngine
|
|
25
|
+
from skills.research_cache.hasher import SourceHasher
|
|
26
|
+
|
|
27
|
+
class SwarmRunner:
|
|
28
|
+
def __init__(self, base_dir: Optional[Path] = None, mock_mode: bool = False):
|
|
29
|
+
self.base_dir = base_dir or Path(".research")
|
|
30
|
+
self.mock_mode = mock_mode
|
|
31
|
+
self.state_machine = ResearchStateMachine(base_dir=self.base_dir)
|
|
32
|
+
self.auditor = EpistemicAuditorEngine(base_dir=self.base_dir)
|
|
33
|
+
self.hasher = SourceHasher(base_dir=self.base_dir)
|
|
34
|
+
self.prompts_dir = PROJECT_ROOT / "prompts"
|
|
35
|
+
|
|
36
|
+
def run_claude_process(self, prompt: str, system_prompt_file: Optional[Path] = None,
|
|
37
|
+
tools: str = "default") -> str:
|
|
38
|
+
"""Executes a headless Claude Code session via `claude -p`."""
|
|
39
|
+
if self.mock_mode:
|
|
40
|
+
return self._mock_claude_response(prompt)
|
|
41
|
+
|
|
42
|
+
cmd = ["claude", "-p", prompt, "--tools", tools]
|
|
43
|
+
if system_prompt_file and system_prompt_file.exists():
|
|
44
|
+
cmd.extend(["--system-prompt", str(system_prompt_file)])
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
res = subprocess.run(
|
|
48
|
+
cmd,
|
|
49
|
+
capture_output=True,
|
|
50
|
+
text=True,
|
|
51
|
+
check=True,
|
|
52
|
+
cwd=str(PROJECT_ROOT)
|
|
53
|
+
)
|
|
54
|
+
return res.stdout.strip()
|
|
55
|
+
except subprocess.CalledProcessError as e:
|
|
56
|
+
print(f"[ERROR] Claude process failed: {e.stderr}", file=sys.stderr)
|
|
57
|
+
raise RuntimeError(f"Claude execution failed: {e.stderr}")
|
|
58
|
+
|
|
59
|
+
def _mock_claude_response(self, prompt: str) -> str:
|
|
60
|
+
"""Mock response generator for unit testing without live API keys."""
|
|
61
|
+
if "Orchestrator" in prompt or "manifest.json" in prompt:
|
|
62
|
+
return json.dumps({
|
|
63
|
+
"session_id": "mock-session-001",
|
|
64
|
+
"objective": "Evaluate ZK prover latency",
|
|
65
|
+
"scopes": [
|
|
66
|
+
{
|
|
67
|
+
"scope_id": "scope_01_latency",
|
|
68
|
+
"title": "Hardware Prover Latency Bounds",
|
|
69
|
+
"objective": "Evaluate Poseidon hash witness generation latency on FPGAs vs GPUs",
|
|
70
|
+
"dependencies": [],
|
|
71
|
+
"affirmative_targets": ["Sub-200ms witness generation on 2^20 constraints"],
|
|
72
|
+
"adversarial_targets": ["PCIe bus bottlenecks during batch streaming"]
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
})
|
|
76
|
+
return "MOCK_RESPONSE"
|
|
77
|
+
|
|
78
|
+
def orchestrate_objective(self, objective: str, frontier_file: Optional[Path] = None) -> List[Dict[str, Any]]:
|
|
79
|
+
"""Phase 1: Run Swarm Orchestrator to decompose the research question."""
|
|
80
|
+
print(f"\nš§ [Phase 1: Orchestration] Decomposing objective: '{objective}'...")
|
|
81
|
+
self.state_machine.init_session(objective)
|
|
82
|
+
self.state_machine.update_session_status(SessionStatus.ORCHESTRATING)
|
|
83
|
+
|
|
84
|
+
frontier_context = ""
|
|
85
|
+
if frontier_file and frontier_file.exists():
|
|
86
|
+
with open(frontier_file, "r", encoding="utf-8") as f:
|
|
87
|
+
frontier_data = json.load(f)
|
|
88
|
+
frontier_context = f"\nSETTLED CONSTRAINTS FROM FRONTIER:\n{json.dumps(frontier_data.get('settled_constraints', {}), indent=2)}"
|
|
89
|
+
|
|
90
|
+
orchestrator_prompt = f"""
|
|
91
|
+
You are the Swarm Orchestrator. Read prompts/orchestrator.md.
|
|
92
|
+
Objective: {objective}
|
|
93
|
+
{frontier_context}
|
|
94
|
+
|
|
95
|
+
Output ONLY valid JSON representing the scope decomposition conforming to prompts/orchestrator.md.
|
|
96
|
+
"""
|
|
97
|
+
system_prompt = self.prompts_dir / "orchestrator.md"
|
|
98
|
+
raw_output = self.run_claude_process(orchestrator_prompt, system_prompt_file=system_prompt)
|
|
99
|
+
|
|
100
|
+
# Parse JSON
|
|
101
|
+
try:
|
|
102
|
+
# Handle potential markdown fence blocks
|
|
103
|
+
clean_json = raw_output
|
|
104
|
+
if "```json" in clean_json:
|
|
105
|
+
clean_json = clean_json.split("```json")[1].split("```")[0]
|
|
106
|
+
elif "```" in clean_json:
|
|
107
|
+
clean_json = clean_json.split("```")[1].split("```")[0]
|
|
108
|
+
manifest_data = json.loads(clean_json.strip())
|
|
109
|
+
scopes = manifest_data.get("scopes", [])
|
|
110
|
+
except Exception as e:
|
|
111
|
+
print(f"[WARN] Failed to parse JSON from orchestrator output: {e}. Using fallback decomposition.")
|
|
112
|
+
scopes = [
|
|
113
|
+
{
|
|
114
|
+
"scope_id": "scope_01_primary_investigation",
|
|
115
|
+
"title": f"Investigation: {objective[:40]}",
|
|
116
|
+
"objective": objective,
|
|
117
|
+
"dependencies": [],
|
|
118
|
+
"affirmative_targets": ["Find corroborating empirical data"],
|
|
119
|
+
"adversarial_targets": ["Probe counter-arguments and failure modes"]
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
self.state_machine.set_scopes(scopes)
|
|
124
|
+
print(f"ā
Generated {len(scopes)} decoupled dialectic scopes.")
|
|
125
|
+
return scopes
|
|
126
|
+
|
|
127
|
+
def run_agent_alpha(self, scope: Dict[str, Any]):
|
|
128
|
+
"""Executes Agent Alpha (Thesis / Proponent) for a scope."""
|
|
129
|
+
scope_id = scope["scope_id"]
|
|
130
|
+
print(f" [Alpha] šļø Starting Agent Alpha (Thesis) on [{scope_id}]...")
|
|
131
|
+
|
|
132
|
+
if self.mock_mode:
|
|
133
|
+
# Create synthetic cached source
|
|
134
|
+
sample_content = "# FPGA Prover Benchmark\nOur FPGA pipeline executes the Poseidon round constraints in 184ms with a peak memory bandwidth of 45 GB/s."
|
|
135
|
+
shash = self.hasher.store_source("https://arxiv.org/abs/2405.0001", sample_content, "FPGA Benchmark")
|
|
136
|
+
|
|
137
|
+
dossier = {
|
|
138
|
+
"agent": "Agent Alpha (Thesis)",
|
|
139
|
+
"scope_id": scope_id,
|
|
140
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
141
|
+
"affirmative_claims": [
|
|
142
|
+
{
|
|
143
|
+
"claim_id": "ALPHA-C01",
|
|
144
|
+
"tag": "VERIFIED",
|
|
145
|
+
"statement": "FPGA-accelerated Poseidon provers achieve sub-200ms latency on 2^20 constraints.",
|
|
146
|
+
"source_hash": shash,
|
|
147
|
+
"source_url": "https://arxiv.org/abs/2405.0001",
|
|
148
|
+
"verbatim_quote": "Our FPGA pipeline executes the Poseidon round constraints in 184ms with a peak memory bandwidth of 45 GB/s."
|
|
149
|
+
}
|
|
150
|
+
],
|
|
151
|
+
"inferred_implications": [
|
|
152
|
+
{
|
|
153
|
+
"inference_id": "ALPHA-I01",
|
|
154
|
+
"tag": "INFERRED",
|
|
155
|
+
"statement": "Hardware provers satisfy 1-second block finality bounds.",
|
|
156
|
+
"parent_claims": ["ALPHA-C01"],
|
|
157
|
+
"deductive_logic": "184ms << 1000ms target."
|
|
158
|
+
}
|
|
159
|
+
],
|
|
160
|
+
"negative_knowledge": []
|
|
161
|
+
}
|
|
162
|
+
else:
|
|
163
|
+
prompt = f"Run Agent Alpha for scope: {json.dumps(scope)}. Save findings to {scope_id} scratchpad."
|
|
164
|
+
system_prompt = self.prompts_dir / "agent_alpha_thesis.md"
|
|
165
|
+
self.run_claude_process(prompt, system_prompt_file=system_prompt)
|
|
166
|
+
# Read created dossier from scratchpad
|
|
167
|
+
dossier_path = self.state_machine.get_scope_dir(scope_id) / "alpha_dossier.json"
|
|
168
|
+
with open(dossier_path, "r", encoding="utf-8") as f:
|
|
169
|
+
dossier = json.load(f)
|
|
170
|
+
|
|
171
|
+
self.state_machine.record_agent_completion(scope_id, "alpha", dossier)
|
|
172
|
+
print(f" [Alpha] ā
Completed Agent Alpha for [{scope_id}].")
|
|
173
|
+
|
|
174
|
+
def run_agent_beta(self, scope: Dict[str, Any]):
|
|
175
|
+
"""Executes Agent Beta (Antithesis / Red Team) for a scope."""
|
|
176
|
+
scope_id = scope["scope_id"]
|
|
177
|
+
print(f" [Beta] šÆ Starting Agent Beta (Red Team) on [{scope_id}]...")
|
|
178
|
+
|
|
179
|
+
if self.mock_mode:
|
|
180
|
+
sample_content = "# PCIe Bus Saturation Study\nIn continuous batch streaming, PCIe 4.0 transfers introduce a 650ms delay, yielding total latency > 800ms."
|
|
181
|
+
shash = self.hasher.store_source("https://arxiv.org/abs/2406.9999", sample_content, "PCIe Bottlenecks")
|
|
182
|
+
|
|
183
|
+
dossier = {
|
|
184
|
+
"agent": "Agent Beta (Red Team)",
|
|
185
|
+
"scope_id": scope_id,
|
|
186
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
187
|
+
"falsification_claims": [
|
|
188
|
+
{
|
|
189
|
+
"claim_id": "BETA-C01",
|
|
190
|
+
"tag": "VERIFIED",
|
|
191
|
+
"statement": "Batch streaming incurs a 650ms PCIe transfer delay under production loads.",
|
|
192
|
+
"source_hash": shash,
|
|
193
|
+
"source_url": "https://arxiv.org/abs/2406.9999",
|
|
194
|
+
"verbatim_quote": "In continuous batch streaming, PCIe 4.0 transfers introduce a 650ms delay, yielding total latency > 800ms."
|
|
195
|
+
}
|
|
196
|
+
],
|
|
197
|
+
"methodological_critiques": [
|
|
198
|
+
{
|
|
199
|
+
"target_assertion": "FPGA-accelerated Poseidon provers achieve sub-200ms latency on 2^20 constraints.",
|
|
200
|
+
"critique": "Benchmark isolates compute kernel and ignores host-to-device PCIe latency in pipelined batches.",
|
|
201
|
+
"evidence_hash": shash
|
|
202
|
+
}
|
|
203
|
+
],
|
|
204
|
+
"negative_knowledge": [
|
|
205
|
+
{
|
|
206
|
+
"query": "Zero-latency PCIe streaming ZK provers",
|
|
207
|
+
"finding": "No architecture eliminates bus transfer overhead without on-chip memory > 128GB."
|
|
208
|
+
}
|
|
209
|
+
]
|
|
210
|
+
}
|
|
211
|
+
else:
|
|
212
|
+
prompt = f"Run Agent Beta for scope: {json.dumps(scope)}. Save findings to {scope_id} scratchpad."
|
|
213
|
+
system_prompt = self.prompts_dir / "agent_beta_antithesis.md"
|
|
214
|
+
self.run_claude_process(prompt, system_prompt_file=system_prompt)
|
|
215
|
+
dossier_path = self.state_machine.get_scope_dir(scope_id) / "beta_dossier.json"
|
|
216
|
+
with open(dossier_path, "r", encoding="utf-8") as f:
|
|
217
|
+
dossier = json.load(f)
|
|
218
|
+
|
|
219
|
+
self.state_machine.record_agent_completion(scope_id, "beta", dossier)
|
|
220
|
+
print(f" [Beta] ā
Completed Agent Beta for [{scope_id}].")
|
|
221
|
+
|
|
222
|
+
def execute_scope_dialectic(self, scope: Dict[str, Any]):
|
|
223
|
+
"""Dispatches Agent Alpha and Agent Beta concurrently."""
|
|
224
|
+
scope_id = scope["scope_id"]
|
|
225
|
+
print(f"\nā” [Swarm Dispatch] Launching Dialectic Pair for [{scope_id}]: '{scope.get('title')}'")
|
|
226
|
+
self.state_machine.update_scope_status(scope_id, ScopeStatus.RUNNING_PARALLEL)
|
|
227
|
+
|
|
228
|
+
with ThreadPoolExecutor(max_workers=2) as executor:
|
|
229
|
+
future_alpha = executor.submit(self.run_agent_alpha, scope)
|
|
230
|
+
future_beta = executor.submit(self.run_agent_beta, scope)
|
|
231
|
+
|
|
232
|
+
# Wait for both
|
|
233
|
+
future_alpha.result()
|
|
234
|
+
future_beta.result()
|
|
235
|
+
|
|
236
|
+
# Phase 4: Run Epistemic Auditor
|
|
237
|
+
print(f"āļø [Auditor] Auditing evidence & computing divergence for [{scope_id}]...")
|
|
238
|
+
audit_report = self.auditor.audit_scope(scope_id)
|
|
239
|
+
summary = audit_report["summary"]
|
|
240
|
+
print(f" [Audit Result] Score: {summary['epistemic_score']}/1.0 | Divergence: {summary['divergence_score']} | Verified: {summary['verified_passed']} | Rejected: {summary['unverified_rejected']}")
|
|
241
|
+
|
|
242
|
+
def run_swarm(self, objective: str, frontier_file: Optional[Path] = None):
|
|
243
|
+
"""Full end-to-end execution loop."""
|
|
244
|
+
start_time = datetime.now(timezone.utc)
|
|
245
|
+
print("=" * 70)
|
|
246
|
+
print("š EPISTEMIC SWARM: HIGH-INTEGRITY RESEARCH HARNESS")
|
|
247
|
+
print("=" * 70)
|
|
248
|
+
|
|
249
|
+
# 1. Orchestrate
|
|
250
|
+
scopes = self.orchestrate_objective(objective, frontier_file)
|
|
251
|
+
|
|
252
|
+
# 2. Execute scopes according to DAG
|
|
253
|
+
while True:
|
|
254
|
+
ready_scopes = self.state_machine.get_ready_scopes()
|
|
255
|
+
if not ready_scopes:
|
|
256
|
+
# Check if all scopes are complete
|
|
257
|
+
manifest = self.state_machine.load_global_manifest()
|
|
258
|
+
all_complete = all(
|
|
259
|
+
self.state_machine.load_scope_manifest(s["scope_id"]).get("status") == ScopeStatus.COMPLETE.value
|
|
260
|
+
for s in manifest["scopes"]
|
|
261
|
+
)
|
|
262
|
+
if all_complete:
|
|
263
|
+
break
|
|
264
|
+
else:
|
|
265
|
+
print("[ERROR] Deadlock in scope dependency graph.", file=sys.stderr)
|
|
266
|
+
self.state_machine.update_session_status(SessionStatus.FAILED)
|
|
267
|
+
return
|
|
268
|
+
|
|
269
|
+
for scope in ready_scopes:
|
|
270
|
+
self.execute_scope_dialectic(scope)
|
|
271
|
+
|
|
272
|
+
# 3. Master Synthesis Compilation
|
|
273
|
+
print("\nš [Phase 5: Master Synthesis] Aggregating scope dossiers...")
|
|
274
|
+
self._compile_master_synthesis(objective)
|
|
275
|
+
self.state_machine.update_session_status(SessionStatus.COMPLETED)
|
|
276
|
+
|
|
277
|
+
duration = (datetime.now(timezone.utc) - start_time).total_seconds()
|
|
278
|
+
print(f"\nš Research swarm completed in {duration:.1f}s. Report: .research/final_synthesis.md")
|
|
279
|
+
|
|
280
|
+
def _compile_master_synthesis(self, objective: str):
|
|
281
|
+
manifest = self.state_machine.load_global_manifest()
|
|
282
|
+
synthesis_lines = [
|
|
283
|
+
f"# Master Epistemic Research Report: {objective}\n",
|
|
284
|
+
f"**Session ID**: `{manifest['session_id']}` | **Generated**: `{manifest['updated_at']}`\n",
|
|
285
|
+
"## Executive Summary",
|
|
286
|
+
"This report was compiled using the Epistemic Swarm dialectic harness. Every factual statement carries an empirical verification pointer backed by a content-addressed raw document cache.\n",
|
|
287
|
+
"## Scope Findings & Dialectic Balance Sheets\n"
|
|
288
|
+
]
|
|
289
|
+
|
|
290
|
+
total_verified = 0
|
|
291
|
+
total_rejected = 0
|
|
292
|
+
all_divergences = []
|
|
293
|
+
|
|
294
|
+
for scope in manifest["scopes"]:
|
|
295
|
+
sid = scope["scope_id"]
|
|
296
|
+
scope_dir = self.state_machine.get_scope_dir(sid)
|
|
297
|
+
audit_file = scope_dir / "audit_report.json"
|
|
298
|
+
synth_file = scope_dir / "scope_synthesis.md"
|
|
299
|
+
|
|
300
|
+
if audit_file.exists():
|
|
301
|
+
with open(audit_file, "r", encoding="utf-8") as f:
|
|
302
|
+
ar = json.load(f)
|
|
303
|
+
total_verified += ar["summary"]["verified_passed"]
|
|
304
|
+
total_rejected += ar["summary"]["unverified_rejected"]
|
|
305
|
+
all_divergences.append(ar["summary"]["divergence_score"])
|
|
306
|
+
|
|
307
|
+
if synth_file.exists():
|
|
308
|
+
with open(synth_file, "r", encoding="utf-8") as f:
|
|
309
|
+
synthesis_lines.append(f.read())
|
|
310
|
+
synthesis_lines.append("\n---\n")
|
|
311
|
+
|
|
312
|
+
avg_div = round(sum(all_divergences) / max(1, len(all_divergences)), 2)
|
|
313
|
+
synthesis_lines.append(f"\n## Swarm Epistemic Audit Totals\n")
|
|
314
|
+
synthesis_lines.append(f"- **Total Verified Primary Citations**: `{total_verified}`")
|
|
315
|
+
synthesis_lines.append(f"- **Total Unverified Claims Purged**: `{total_rejected}`")
|
|
316
|
+
synthesis_lines.append(f"- **Mean Swarm Divergence Score**: `{avg_div}`")
|
|
317
|
+
|
|
318
|
+
final_path = self.base_dir / "final_synthesis.md"
|
|
319
|
+
with open(final_path, "w", encoding="utf-8") as f:
|
|
320
|
+
f.write("\n".join(synthesis_lines))
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def main():
|
|
324
|
+
parser = argparse.ArgumentParser(description="Epistemic Swarm Dialectic Research Runner")
|
|
325
|
+
parser.add_argument("--objective", type=str, required=True, help="Research question or objective")
|
|
326
|
+
parser.add_argument("--frontier", type=str, help="Path to settled frontier.json from /grilling")
|
|
327
|
+
parser.add_argument("--mock-claude", action="store_true", help="Run with synthetic test data without invoking Claude Code")
|
|
328
|
+
parser.add_argument("--dir", default=".research", help="Path to .research workspace")
|
|
329
|
+
|
|
330
|
+
args = parser.parse_args()
|
|
331
|
+
frontier_path = Path(args.frontier) if args.frontier else None
|
|
332
|
+
runner = SwarmRunner(base_dir=Path(args.dir), mock_mode=args.mock_claude)
|
|
333
|
+
runner.run_swarm(args.objective, frontier_file=frontier_path)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
if __name__ == "__main__":
|
|
337
|
+
main()
|