hypermind 0.11.0__py3-none-any.whl
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.
- hmctl/__init__.py +7 -0
- hmctl/cli.py +947 -0
- hypermind/__init__.py +208 -0
- hypermind/_deprecations.py +108 -0
- hypermind/agent.py +2896 -0
- hypermind/agent_card.py +127 -0
- hypermind/api/__init__.py +32 -0
- hypermind/api/app.py +686 -0
- hypermind/capability.py +923 -0
- hypermind/consult.py +493 -0
- hypermind/consult_bias.py +112 -0
- hypermind/contract_net.py +219 -0
- hypermind/crypto/__init__.py +21 -0
- hypermind/crypto/cose_encrypt.py +126 -0
- hypermind/crypto/dkg.py +246 -0
- hypermind/crypto/domain.py +59 -0
- hypermind/crypto/frost.py +771 -0
- hypermind/crypto/hashing.py +38 -0
- hypermind/crypto/hlc.py +163 -0
- hypermind/crypto/hpke.py +277 -0
- hypermind/crypto/kms.py +196 -0
- hypermind/crypto/kms_backends/__init__.py +56 -0
- hypermind/crypto/kms_backends/aws.py +128 -0
- hypermind/crypto/kms_backends/azure.py +114 -0
- hypermind/crypto/kms_backends/gcp.py +97 -0
- hypermind/crypto/kms_backends/vault.py +120 -0
- hypermind/crypto/namespace_root.py +204 -0
- hypermind/crypto/pq.py +152 -0
- hypermind/crypto/room_epoch.py +97 -0
- hypermind/crypto/signing.py +245 -0
- hypermind/deliberation.py +355 -0
- hypermind/did_web.py +256 -0
- hypermind/dispute.py +858 -0
- hypermind/errors.py +218 -0
- hypermind/eval/__init__.py +49 -0
- hypermind/eval/calibration.py +307 -0
- hypermind/eval/comparison.py +251 -0
- hypermind/eval/replay.py +202 -0
- hypermind/eval/swarm_iq.py +718 -0
- hypermind/knowledge/__init__.py +17 -0
- hypermind/knowledge/citation_graph.py +141 -0
- hypermind/knowledge/correlated_agreement.py +167 -0
- hypermind/knowledge/embedding_registry.py +115 -0
- hypermind/knowledge/kernel_store.py +197 -0
- hypermind/knowledge/rekor.py +173 -0
- hypermind/knowledge/reputation.py +364 -0
- hypermind/knowledge/swarm_memory.py +523 -0
- hypermind/knowledge/transparency.py +409 -0
- hypermind/mcp/__init__.py +60 -0
- hypermind/mcp/bridge.py +151 -0
- hypermind/mcp/client.py +142 -0
- hypermind/mcp/provenance.py +214 -0
- hypermind/mcp/relata_tools.py +152 -0
- hypermind/mcp/server.py +248 -0
- hypermind/mcp/transport.py +206 -0
- hypermind/mind/__init__.py +150 -0
- hypermind/mind/active.py +234 -0
- hypermind/mind/belief.py +369 -0
- hypermind/mind/deliberator.py +625 -0
- hypermind/mind/diversity.py +144 -0
- hypermind/mind/evolve.py +184 -0
- hypermind/mind/federation.py +154 -0
- hypermind/mind/goals.py +117 -0
- hypermind/mind/integration.py +190 -0
- hypermind/mind/mediator.py +74 -0
- hypermind/mind/memory_store.py +129 -0
- hypermind/mind/policy.py +307 -0
- hypermind/mind/records.py +260 -0
- hypermind/mind/roles.py +190 -0
- hypermind/mind/sybil_guard.py +41 -0
- hypermind/mind/world_model.py +166 -0
- hypermind/namespace.py +515 -0
- hypermind/namespace_policy.py +254 -0
- hypermind/observability/__init__.py +25 -0
- hypermind/observability/asgi.py +214 -0
- hypermind/observability/cost.py +277 -0
- hypermind/observability/otel_export.py +200 -0
- hypermind/observability/recorder.py +344 -0
- hypermind/observability/swarm_trace.py +438 -0
- hypermind/pin_policy.py +116 -0
- hypermind/py.typed +0 -0
- hypermind/responders/__init__.py +87 -0
- hypermind/responders/anthropic.py +159 -0
- hypermind/responders/base.py +162 -0
- hypermind/responders/openai.py +199 -0
- hypermind/responders/router.py +254 -0
- hypermind/responders/structured.py +287 -0
- hypermind/responders/tools.py +444 -0
- hypermind/revocation.py +270 -0
- hypermind/rule_ids.py +135 -0
- hypermind/schemas/encrypted_statement.cddl +28 -0
- hypermind/schemas/namespace_accept.cddl +20 -0
- hypermind/schemas/namespace_create.cddl +25 -0
- hypermind/schemas/namespace_founding_attest.cddl +30 -0
- hypermind/schemas/namespace_invite.cddl +22 -0
- hypermind/schemas/wire-v0.1.cddl +73 -0
- hypermind/simlab/__init__.py +15 -0
- hypermind/simlab/_persona_registry.py +93 -0
- hypermind/simlab/_scenario_impl.py +2778 -0
- hypermind/simlab/app.py +2538 -0
- hypermind/simlab/backends/__init__.py +27 -0
- hypermind/simlab/backends/anchor.py +88 -0
- hypermind/simlab/backends/local.py +32 -0
- hypermind/simlab/backends/server.py +79 -0
- hypermind/simlab/cli_command.py +108 -0
- hypermind/simlab/config.py +106 -0
- hypermind/simlab/deployment.py +26 -0
- hypermind/simlab/knowledge.py +716 -0
- hypermind/simlab/learning.py +295 -0
- hypermind/simlab/modes/__init__.py +115 -0
- hypermind/simlab/modes/_eval_real_llm.py +373 -0
- hypermind/simlab/modes/aar.py +611 -0
- hypermind/simlab/modes/backtest.py +610 -0
- hypermind/simlab/modes/binary_forecast.py +69 -0
- hypermind/simlab/modes/conformance.py +1869 -0
- hypermind/simlab/modes/evaluation.py +2271 -0
- hypermind/simlab/modes/governance.py +648 -0
- hypermind/simlab/modes/redteam.py +534 -0
- hypermind/simlab/modes/tabletop.py +797 -0
- hypermind/simlab/modes/tournament.py +448 -0
- hypermind/simlab/modes/whatif.py +523 -0
- hypermind/simlab/namespace.py +125 -0
- hypermind/simlab/personas.py +477 -0
- hypermind/simlab/prompt_generator.py +172 -0
- hypermind/simlab/question_sets/geopolitics_q20.json +122 -0
- hypermind/simlab/question_sets/governance_q5.json +67 -0
- hypermind/simlab/question_sets/msft_outlook_q10.json +62 -0
- hypermind/simlab/question_sets/whatif_q3.json +38 -0
- hypermind/simlab/registry.py +325 -0
- hypermind/simlab/runner.py +239 -0
- hypermind/simlab/scenario.py +147 -0
- hypermind/simlab/swarms.py +180 -0
- hypermind/simlab/tool_handlers/__init__.py +4 -0
- hypermind/simlab/tool_handlers/alpha_vantage.py +39 -0
- hypermind/simlab/tool_handlers/brave.py +46 -0
- hypermind/simlab/tool_handlers/newsapi.py +50 -0
- hypermind/simlab/tool_handlers/openweather.py +46 -0
- hypermind/simlab/tool_handlers/pinecone.py +52 -0
- hypermind/simlab/tool_handlers/qdrant.py +57 -0
- hypermind/simlab/tool_handlers/query_knowledge_base.py +21 -0
- hypermind/simlab/tool_handlers/relata_recall.py +61 -0
- hypermind/simlab/tool_handlers/retrieve_document.py +21 -0
- hypermind/simlab/tool_handlers/serper.py +46 -0
- hypermind/simlab/tool_handlers/tavily.py +53 -0
- hypermind/simlab/tool_handlers/weaviate.py +65 -0
- hypermind/simlab/tool_handlers/wikipedia.py +64 -0
- hypermind/simlab/tool_handlers/wolfram.py +48 -0
- hypermind/simlab/tool_handlers/yahoo_finance.py +49 -0
- hypermind/simlab/tool_registry.py +568 -0
- hypermind/simlab/topic_adapter.py +338 -0
- hypermind/simlab/topic_questions.py +259 -0
- hypermind/storage/__init__.py +69 -0
- hypermind/storage/backend.py +71 -0
- hypermind/storage/relata.py +245 -0
- hypermind/storage/sqlite.py +258 -0
- hypermind/sync.py +191 -0
- hypermind/tal.py +175 -0
- hypermind/tasks.py +334 -0
- hypermind/testing.py +16 -0
- hypermind/tools/__init__.py +32 -0
- hypermind/tools/registry.py +61 -0
- hypermind/transport/__init__.py +22 -0
- hypermind/transport/anti_entropy.py +708 -0
- hypermind/transport/bus.py +206 -0
- hypermind/transport/knows_delta.py +204 -0
- hypermind/transport/libp2p.py +298 -0
- hypermind/transport/placement.py +58 -0
- hypermind/transport/tcp.py +797 -0
- hypermind/transport/tls_profile.py +417 -0
- hypermind/types.py +59 -0
- hypermind/uncertainty.py +222 -0
- hypermind/wire.py +897 -0
- hypermind/workflow/__init__.py +72 -0
- hypermind/workflow/engine.py +655 -0
- hypermind/workflow/plan.py +182 -0
- hypermind/workflow/repair.py +203 -0
- hypermind-0.11.0.dist-info/METADATA +524 -0
- hypermind-0.11.0.dist-info/RECORD +181 -0
- hypermind-0.11.0.dist-info/WHEEL +4 -0
- hypermind-0.11.0.dist-info/entry_points.txt +2 -0
- hypermind-0.11.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "geo-001",
|
|
4
|
+
"query": "Will armed conflict between two major powers begin within 30 days given mobilisation of 50,000+ troops near a disputed border?",
|
|
5
|
+
"ground_truth": 0.65,
|
|
6
|
+
"category": "kinetic"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"id": "geo-002",
|
|
10
|
+
"query": "Is satellite imagery showing tank columns near a border consistent with invasion preparation rather than routine exercise?",
|
|
11
|
+
"ground_truth": 0.75,
|
|
12
|
+
"category": "kinetic"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"id": "geo-003",
|
|
16
|
+
"query": "Will civilian casualties exceed 1,000 in the next 7 days following such mobilisation?",
|
|
17
|
+
"ground_truth": 0.55,
|
|
18
|
+
"category": "kinetic"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "geo-004",
|
|
22
|
+
"query": "Is diplomatic de-escalation viable when both parties have already broken off ambassadorial relations?",
|
|
23
|
+
"ground_truth": 0.30,
|
|
24
|
+
"category": "diplomatic"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"id": "geo-005",
|
|
28
|
+
"query": "Will a UN Security Council resolution pass condemning the aggression?",
|
|
29
|
+
"ground_truth": 0.40,
|
|
30
|
+
"category": "diplomatic"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "geo-006",
|
|
34
|
+
"query": "Will currency markets price in war risk via a >5% drop in the affected nation's currency within 7 days?",
|
|
35
|
+
"ground_truth": 0.70,
|
|
36
|
+
"category": "economic"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": "geo-007",
|
|
40
|
+
"query": "Will sovereign CDS spreads widen to >300bps within 14 days of escalation?",
|
|
41
|
+
"ground_truth": 0.65,
|
|
42
|
+
"category": "economic"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": "geo-008",
|
|
46
|
+
"query": "Will sanctions be imposed within 30 days of the first kinetic action?",
|
|
47
|
+
"ground_truth": 0.85,
|
|
48
|
+
"category": "policy"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"id": "geo-009",
|
|
52
|
+
"query": "Will mass civilian displacement exceed 100,000 in the first week?",
|
|
53
|
+
"ground_truth": 0.50,
|
|
54
|
+
"category": "humanitarian"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"id": "geo-010",
|
|
58
|
+
"query": "Will critical infrastructure (power grid, water) be deliberately targeted in the first 14 days?",
|
|
59
|
+
"ground_truth": 0.45,
|
|
60
|
+
"category": "kinetic"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"id": "geo-011",
|
|
64
|
+
"query": "Will a third-party state intervene militarily within 60 days?",
|
|
65
|
+
"ground_truth": 0.15,
|
|
66
|
+
"category": "kinetic"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"id": "geo-012",
|
|
70
|
+
"query": "Will a cyberattack on financial infrastructure precede or accompany kinetic operations?",
|
|
71
|
+
"ground_truth": 0.55,
|
|
72
|
+
"category": "cyber"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"id": "geo-013",
|
|
76
|
+
"query": "Will there be a credible nuclear-doctrine signal (alert posture, treaty withdrawal) in the conflict's first month?",
|
|
77
|
+
"ground_truth": 0.20,
|
|
78
|
+
"category": "kinetic"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"id": "geo-014",
|
|
82
|
+
"query": "Will the conflict last longer than 90 days?",
|
|
83
|
+
"ground_truth": 0.65,
|
|
84
|
+
"category": "kinetic"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"id": "geo-015",
|
|
88
|
+
"query": "Will a ceasefire mediated by a neutral state be agreed within 60 days?",
|
|
89
|
+
"ground_truth": 0.25,
|
|
90
|
+
"category": "diplomatic"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"id": "geo-016",
|
|
94
|
+
"query": "Will domestic protests in the aggressor state exceed 100,000 participants in any single city?",
|
|
95
|
+
"ground_truth": 0.30,
|
|
96
|
+
"category": "domestic"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"id": "geo-017",
|
|
100
|
+
"query": "Will commodity prices (oil, wheat) spike >15% within 30 days?",
|
|
101
|
+
"ground_truth": 0.55,
|
|
102
|
+
"category": "economic"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"id": "geo-018",
|
|
106
|
+
"query": "Will a NATO Article 4 consultation be triggered within 14 days?",
|
|
107
|
+
"ground_truth": 0.40,
|
|
108
|
+
"category": "policy"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"id": "geo-019",
|
|
112
|
+
"query": "Will the International Criminal Court open a formal investigation within 60 days?",
|
|
113
|
+
"ground_truth": 0.55,
|
|
114
|
+
"category": "policy"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"id": "geo-020",
|
|
118
|
+
"query": "Will a regime-change attempt occur in the aggressor state within 12 months of the conflict's start?",
|
|
119
|
+
"ground_truth": 0.10,
|
|
120
|
+
"category": "domestic"
|
|
121
|
+
}
|
|
122
|
+
]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "gov-001",
|
|
4
|
+
"label": "High-risk classifier",
|
|
5
|
+
"model_card": {
|
|
6
|
+
"model_name": "credit-decision-classifier-v2",
|
|
7
|
+
"intended_use": "Score loan applications for retail banking; outputs feed an underwriter dashboard.",
|
|
8
|
+
"risk_tier": "high",
|
|
9
|
+
"dataset_provenance": "internal application history 2018-2024, documented",
|
|
10
|
+
"mitigations": ["fairness-audit-q3", "appeal-channel", "human-in-the-loop"],
|
|
11
|
+
"deployment_scope": "b2b-customer",
|
|
12
|
+
"autonomous_action": false
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "gov-002",
|
|
17
|
+
"label": "Mass-personalisation LLM",
|
|
18
|
+
"model_card": {
|
|
19
|
+
"model_name": "story-personalisation-llm",
|
|
20
|
+
"intended_use": "Generate personalised marketing copy variants at scale.",
|
|
21
|
+
"risk_tier": "limited",
|
|
22
|
+
"dataset_provenance": "publicly licensed corpora + synthetic augmentations",
|
|
23
|
+
"mitigations": ["rate-limit-100rpm", "content-filter"],
|
|
24
|
+
"deployment_scope": "public-api",
|
|
25
|
+
"autonomous_action": false
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "gov-003",
|
|
30
|
+
"label": "Clinical decision support",
|
|
31
|
+
"model_card": {
|
|
32
|
+
"model_name": "ddx-helper-research",
|
|
33
|
+
"intended_use": "Surface differential-diagnosis options and supporting evidence to clinicians.",
|
|
34
|
+
"risk_tier": "high",
|
|
35
|
+
"dataset_provenance": "MIMIC-IV (research IRB), licensed PubMed abstracts",
|
|
36
|
+
"mitigations": ["clinician-review-required", "exemption-§3060", "audit-log"],
|
|
37
|
+
"deployment_scope": "internal-only",
|
|
38
|
+
"autonomous_action": false
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "gov-004",
|
|
43
|
+
"label": "Autonomous action agent",
|
|
44
|
+
"model_card": {
|
|
45
|
+
"model_name": "auto-refund-agent-v1",
|
|
46
|
+
"intended_use": "Process small-amount refunds end-to-end without human review.",
|
|
47
|
+
"risk_tier": "high",
|
|
48
|
+
"dataset_provenance": "",
|
|
49
|
+
"mitigations": ["amount-cap-50usd"],
|
|
50
|
+
"deployment_scope": "consumer-app",
|
|
51
|
+
"autonomous_action": true
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"id": "gov-005",
|
|
56
|
+
"label": "Children's data product",
|
|
57
|
+
"model_card": {
|
|
58
|
+
"model_name": "edu-tutor-k12",
|
|
59
|
+
"intended_use": "Personalised tutoring assistant for K-12 students.",
|
|
60
|
+
"risk_tier": "high",
|
|
61
|
+
"dataset_provenance": "COPPA-compliant licensed corpora, documented",
|
|
62
|
+
"mitigations": ["coppa-review", "age-gate", "no-pii-retention", "human-review-of-flagged"],
|
|
63
|
+
"deployment_scope": "consumer-app",
|
|
64
|
+
"autonomous_action": false
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
]
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "msft-001",
|
|
4
|
+
"query": "Will Microsoft's stock price be HIGHER 12 months from now than today?",
|
|
5
|
+
"ground_truth": 0.62,
|
|
6
|
+
"category": "price"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"id": "msft-002",
|
|
10
|
+
"query": "Will Azure cloud revenue growth remain ABOVE 25% year-over-year over the next four quarters?",
|
|
11
|
+
"ground_truth": 0.58,
|
|
12
|
+
"category": "fundamentals"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"id": "msft-003",
|
|
16
|
+
"query": "Will Microsoft generate positive ROI on its AI capex (≥$50B committed) within the next 24 months?",
|
|
17
|
+
"ground_truth": 0.55,
|
|
18
|
+
"category": "ai"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "msft-004",
|
|
22
|
+
"query": "Will Microsoft face a MAJOR antitrust action (formal proceedings, blocked deal, or fine >$1B) within 18 months?",
|
|
23
|
+
"ground_truth": 0.32,
|
|
24
|
+
"category": "regulatory"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"id": "msft-005",
|
|
28
|
+
"query": "Will Microsoft's operating margin remain ABOVE 40% over the next four quarters?",
|
|
29
|
+
"ground_truth": 0.72,
|
|
30
|
+
"category": "fundamentals"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "msft-006",
|
|
34
|
+
"query": "Will Microsoft outperform the S&P 500 over the next 12 months?",
|
|
35
|
+
"ground_truth": 0.55,
|
|
36
|
+
"category": "price"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": "msft-007",
|
|
40
|
+
"query": "Will Copilot reach 50M paid seats within 18 months?",
|
|
41
|
+
"ground_truth": 0.60,
|
|
42
|
+
"category": "ai"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": "msft-008",
|
|
46
|
+
"query": "Will Microsoft acquire a major AI company ($10B+) in the next 24 months?",
|
|
47
|
+
"ground_truth": 0.30,
|
|
48
|
+
"category": "m&a"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"id": "msft-009",
|
|
52
|
+
"query": "Will Microsoft's relationship with OpenAI fundamentally restructure (replaced by exclusive license, equity-only stake, or termination) within 24 months?",
|
|
53
|
+
"ground_truth": 0.40,
|
|
54
|
+
"category": "ai"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"id": "msft-010",
|
|
58
|
+
"query": "Will Microsoft initiate a major buyback program (>$50B authorisation) in the next 18 months?",
|
|
59
|
+
"ground_truth": 0.55,
|
|
60
|
+
"category": "capital"
|
|
61
|
+
}
|
|
62
|
+
]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "wif-001",
|
|
4
|
+
"label": "UAE leaves OPEC by end of 2026",
|
|
5
|
+
"payload": {
|
|
6
|
+
"hypothesis": "UAE leaves OPEC by end of 2026.",
|
|
7
|
+
"horizon_days": 365,
|
|
8
|
+
"drivers": [
|
|
9
|
+
"oil_price_usd=60|80|100",
|
|
10
|
+
"us_saudi_relations=warm|cool|cold"
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"id": "wif-002",
|
|
16
|
+
"label": "Fed pivots within 6 months",
|
|
17
|
+
"payload": {
|
|
18
|
+
"hypothesis": "The Federal Reserve pivots to rate cuts within 6 months.",
|
|
19
|
+
"horizon_days": 180,
|
|
20
|
+
"drivers": [
|
|
21
|
+
"core_inflation_yoy=below_2|2_to_3|above_3",
|
|
22
|
+
"labor_market=soft|balanced|tight"
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"id": "wif-003",
|
|
28
|
+
"label": "Major cyber-incident at a named cloud",
|
|
29
|
+
"payload": {
|
|
30
|
+
"hypothesis": "A nation-state cyber-incident affects a major cloud provider within 12 months.",
|
|
31
|
+
"horizon_days": 365,
|
|
32
|
+
"drivers": [
|
|
33
|
+
"actor_capability=nation_state|criminal|insider",
|
|
34
|
+
"blast_radius=single_tenant|regional|global"
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
]
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
"""Sim registry — persistent on-disk record of every simulation run.
|
|
2
|
+
|
|
3
|
+
Layout (under ``$HYPERMIND_HOME/sims/`` or ``~/.hypermind/sims/``):
|
|
4
|
+
|
|
5
|
+
{sim_id}/
|
|
6
|
+
config.json — what was requested (SimConfig.to_dict())
|
|
7
|
+
trace.json — what was produced (full Trace)
|
|
8
|
+
status.json — lifecycle: queued|running|done|failed + progress
|
|
9
|
+
stderr.log — captured stderr from the run
|
|
10
|
+
|
|
11
|
+
A `sim_id` is ``YYYY-MM-DDTHH-MM-SS_xxxxxx`` — sortable lexicographically,
|
|
12
|
+
collision-resistant via a 6-char nonce.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import secrets
|
|
20
|
+
import time
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def home() -> Path:
|
|
27
|
+
"""Resolve the SimLab home dir.
|
|
28
|
+
|
|
29
|
+
Priority:
|
|
30
|
+
1. ``HYPERMIND_HOME`` environment variable
|
|
31
|
+
2. ``~/.hypermind``
|
|
32
|
+
"""
|
|
33
|
+
raw = os.environ.get("HYPERMIND_HOME", "").strip()
|
|
34
|
+
if raw:
|
|
35
|
+
return Path(raw).expanduser()
|
|
36
|
+
return Path.home() / ".hypermind"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def sims_dir() -> Path:
|
|
40
|
+
d = home() / "sims"
|
|
41
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
42
|
+
return d
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
#: Crockford base32 alphabet — no I/L/O/U so the slug stays unambiguous.
|
|
46
|
+
_B32 = "0123456789abcdefghjkmnpqrstvwxyz"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _b32encode(n: int, length: int) -> str:
|
|
50
|
+
"""Tiny base32 encoder — produces `length` chars from the low bits of n."""
|
|
51
|
+
out: list[str] = []
|
|
52
|
+
for _ in range(length):
|
|
53
|
+
out.append(_B32[n & 0x1F])
|
|
54
|
+
n >>= 5
|
|
55
|
+
return "".join(reversed(out))
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def new_sim_id() -> str:
|
|
59
|
+
"""Generate a short ULID-style sim slug.
|
|
60
|
+
|
|
61
|
+
Format: ``YYYY-MM-DD-XXXXXXXXXX`` (10-char base32 random tail).
|
|
62
|
+
|
|
63
|
+
- Date prefix keeps directories scannable in `ls` by day.
|
|
64
|
+
- The 50-bit base32 tail is monotonic-within-process (millisecond
|
|
65
|
+
timestamp + a 30-bit random nonce) so IDs sort by creation time
|
|
66
|
+
*within* a day too — the same property UUIDv7 guarantees.
|
|
67
|
+
- 18 chars total, URL-safe (no `/` `:` `+` to escape), readable.
|
|
68
|
+
|
|
69
|
+
Crockford base32 alphabet (no I/L/O/U) — fewer transcription errors
|
|
70
|
+
if a user reads an id off a screen.
|
|
71
|
+
|
|
72
|
+
"""
|
|
73
|
+
now_ms = int(time.time() * 1000)
|
|
74
|
+
today = time.strftime("%Y-%m-%d", time.gmtime(now_ms / 1000))
|
|
75
|
+
# 50-bit tail = 30 bits of millis-since-midnight (covers 24h) ‖ 20
|
|
76
|
+
# bits of fresh entropy. Putting millis in the HIGH bits is what
|
|
77
|
+
# makes lexicographic sort match creation order — the same property
|
|
78
|
+
# UUIDv7 / ULID guarantee. 20 bits of random tail = ~1M nonces per
|
|
79
|
+
# millisecond before a collision is even probable.
|
|
80
|
+
midnight = int(now_ms / 86_400_000) * 86_400_000
|
|
81
|
+
ms_today = (now_ms - midnight) & ((1 << 30) - 1)
|
|
82
|
+
rand = secrets.randbits(20)
|
|
83
|
+
tail = _b32encode((ms_today << 20) | rand, 10)
|
|
84
|
+
return f"{today}-{tail}"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def new_sim_dir() -> tuple[str, Path]:
|
|
88
|
+
"""Allocate a fresh sim directory; returns (sim_id, path)."""
|
|
89
|
+
sim_id = new_sim_id()
|
|
90
|
+
path = sims_dir() / sim_id
|
|
91
|
+
path.mkdir(parents=True, exist_ok=False)
|
|
92
|
+
return sim_id, path
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def sim_dir(sim_id: str) -> Path:
|
|
96
|
+
path = sims_dir() / sim_id
|
|
97
|
+
resolved = path.resolve()
|
|
98
|
+
base = sims_dir().resolve()
|
|
99
|
+
if not str(resolved).startswith(str(base)):
|
|
100
|
+
raise ValueError(f"sim_id escapes sims directory: {sim_id!r}")
|
|
101
|
+
return path
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# ---- atomic write helpers --------------------------------------------------
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _write_json_atomic(path: Path, data: Any, mode: int | None = None) -> None:
|
|
108
|
+
tmp = path.with_suffix(path.suffix + ".tmp")
|
|
109
|
+
tmp.write_text(json.dumps(data, indent=2, default=str))
|
|
110
|
+
if mode is not None:
|
|
111
|
+
import os
|
|
112
|
+
|
|
113
|
+
os.chmod(tmp, mode) # chmod before rename to avoid TOCTOU window
|
|
114
|
+
tmp.replace(path) # POSIX atomic rename
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def write_config(sim_id: str, cfg_dict: dict[str, Any]) -> None:
|
|
118
|
+
_write_json_atomic(sim_dir(sim_id) / "config.json", cfg_dict)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def load_config(sim_id: str) -> dict[str, Any]:
|
|
122
|
+
p = sim_dir(sim_id) / "config.json"
|
|
123
|
+
if not p.exists():
|
|
124
|
+
raise FileNotFoundError(f"no config for sim {sim_id}")
|
|
125
|
+
return json.loads(p.read_text())
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def write_status(sim_id: str, status: dict[str, Any]) -> None:
|
|
129
|
+
_write_json_atomic(sim_dir(sim_id) / "status.json", status)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def load_status(sim_id: str) -> dict[str, Any]:
|
|
133
|
+
p = sim_dir(sim_id) / "status.json"
|
|
134
|
+
if not p.exists():
|
|
135
|
+
return {"sim_id": sim_id, "state": "unknown"}
|
|
136
|
+
return json.loads(p.read_text())
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def update_status(sim_id: str, **fields: Any) -> dict[str, Any]:
|
|
140
|
+
"""Read-modify-write the status.json for a sim."""
|
|
141
|
+
status = load_status(sim_id)
|
|
142
|
+
status.update(fields)
|
|
143
|
+
write_status(sim_id, status)
|
|
144
|
+
return status
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def load_trace(sim_id: str) -> dict[str, Any] | None:
|
|
148
|
+
p = sim_dir(sim_id) / "trace.json"
|
|
149
|
+
if not p.exists():
|
|
150
|
+
return None
|
|
151
|
+
return json.loads(p.read_text())
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# ---- listing ---------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@dataclass
|
|
158
|
+
class SimSummary:
|
|
159
|
+
sim_id: str
|
|
160
|
+
state: str
|
|
161
|
+
started_at: float | None
|
|
162
|
+
finished_at: float | None
|
|
163
|
+
n_agents: int | None
|
|
164
|
+
n_questions: int | None
|
|
165
|
+
ciq_score: float | None
|
|
166
|
+
eig_mean: float | None # S3 — primary headline metric (bits/question)
|
|
167
|
+
model: str | None
|
|
168
|
+
enable_tools: bool | None
|
|
169
|
+
deliberation_rounds: int | None
|
|
170
|
+
no_llm: bool | None
|
|
171
|
+
error: str | None
|
|
172
|
+
topic: str | None # user-entered free-text topic (or empty if saved-set was used)
|
|
173
|
+
questions_set: str | None # legacy saved-set name when no topic
|
|
174
|
+
|
|
175
|
+
def to_dict(self) -> dict[str, Any]:
|
|
176
|
+
return {
|
|
177
|
+
"sim_id": self.sim_id,
|
|
178
|
+
"state": self.state,
|
|
179
|
+
"started_at": self.started_at,
|
|
180
|
+
"finished_at": self.finished_at,
|
|
181
|
+
"n_agents": self.n_agents,
|
|
182
|
+
"n_questions": self.n_questions,
|
|
183
|
+
"ciq_score": self.ciq_score,
|
|
184
|
+
"eig_mean": self.eig_mean,
|
|
185
|
+
"model": self.model,
|
|
186
|
+
"enable_tools": self.enable_tools,
|
|
187
|
+
"deliberation_rounds": self.deliberation_rounds,
|
|
188
|
+
"no_llm": self.no_llm,
|
|
189
|
+
"error": self.error,
|
|
190
|
+
"topic": self.topic,
|
|
191
|
+
"questions_set": self.questions_set,
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _summary_from_dir(d: Path) -> SimSummary | None:
|
|
196
|
+
sim_id = d.name
|
|
197
|
+
if not d.is_dir():
|
|
198
|
+
return None
|
|
199
|
+
status = load_status(sim_id)
|
|
200
|
+
config_path = d / "config.json"
|
|
201
|
+
cfg: dict[str, Any] = {}
|
|
202
|
+
if config_path.exists():
|
|
203
|
+
try:
|
|
204
|
+
cfg = json.loads(config_path.read_text())
|
|
205
|
+
except json.JSONDecodeError:
|
|
206
|
+
cfg = {}
|
|
207
|
+
# n_agents, n_questions, ciq_score, eig_mean are written to status.json
|
|
208
|
+
# at completion by the runner — no need to read trace.json for list views.
|
|
209
|
+
return SimSummary(
|
|
210
|
+
sim_id=sim_id,
|
|
211
|
+
state=status.get("state", "unknown"),
|
|
212
|
+
started_at=status.get("started_at"),
|
|
213
|
+
finished_at=status.get("finished_at"),
|
|
214
|
+
n_agents=status.get("n_agents") or cfg.get("personas", 0) * cfg.get("replicas", 0),
|
|
215
|
+
n_questions=status.get("n_questions"),
|
|
216
|
+
ciq_score=status.get("ciq_score"),
|
|
217
|
+
eig_mean=status.get("eig_mean"),
|
|
218
|
+
model=cfg.get("model"),
|
|
219
|
+
enable_tools=cfg.get("enable_tools"),
|
|
220
|
+
deliberation_rounds=cfg.get("deliberation_rounds"),
|
|
221
|
+
no_llm=cfg.get("no_llm"),
|
|
222
|
+
error=status.get("error"),
|
|
223
|
+
topic=cfg.get("topic") or None,
|
|
224
|
+
questions_set=cfg.get("questions") or None,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def list_sims(*, limit: int | None = None) -> list[SimSummary]:
|
|
229
|
+
"""Return summaries newest-first. Skips empty/corrupt directories."""
|
|
230
|
+
out: list[SimSummary] = []
|
|
231
|
+
for d in sorted(sims_dir().iterdir(), reverse=True):
|
|
232
|
+
if d.is_dir():
|
|
233
|
+
try:
|
|
234
|
+
s = _summary_from_dir(d)
|
|
235
|
+
if s is not None:
|
|
236
|
+
out.append(s)
|
|
237
|
+
except Exception:
|
|
238
|
+
continue
|
|
239
|
+
if limit is not None and len(out) >= limit:
|
|
240
|
+
break
|
|
241
|
+
return out
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _summary_from_backend(backend: Any, sim_id: str) -> SimSummary | None:
|
|
245
|
+
"""Build a SimSummary from a StorageBackend (server tier).
|
|
246
|
+
|
|
247
|
+
Reads status.json and config.json from the backend, and reads the
|
|
248
|
+
top-level summary fields from trace.json when state == "done".
|
|
249
|
+
Returns None if the sim has no status record.
|
|
250
|
+
"""
|
|
251
|
+
raw_status = backend.read_file(sim_id, "status.json")
|
|
252
|
+
if raw_status is None:
|
|
253
|
+
return None
|
|
254
|
+
try:
|
|
255
|
+
status: dict[str, Any] = json.loads(raw_status)
|
|
256
|
+
except (json.JSONDecodeError, ValueError):
|
|
257
|
+
return None
|
|
258
|
+
|
|
259
|
+
cfg: dict[str, Any] = {}
|
|
260
|
+
raw_cfg = backend.read_file(sim_id, "config.json")
|
|
261
|
+
if raw_cfg is not None:
|
|
262
|
+
try:
|
|
263
|
+
cfg = json.loads(raw_cfg)
|
|
264
|
+
except (json.JSONDecodeError, ValueError):
|
|
265
|
+
cfg = {}
|
|
266
|
+
|
|
267
|
+
trace_summary: dict[str, Any] = {}
|
|
268
|
+
if status.get("state") == "done":
|
|
269
|
+
trace = backend.read_trace(sim_id) if hasattr(backend, "read_trace") else None
|
|
270
|
+
if trace is not None:
|
|
271
|
+
try:
|
|
272
|
+
trace_summary = {
|
|
273
|
+
"n_agents": trace.get("n_agents"),
|
|
274
|
+
"n_questions": trace.get("n_questions"),
|
|
275
|
+
"ciq_score": (trace.get("collective_iq") or {}).get("score"),
|
|
276
|
+
"eig_mean": (trace.get("collective_iq") or {}).get("eig_mean"),
|
|
277
|
+
}
|
|
278
|
+
except Exception:
|
|
279
|
+
trace_summary = {}
|
|
280
|
+
|
|
281
|
+
return SimSummary(
|
|
282
|
+
sim_id=sim_id,
|
|
283
|
+
state=status.get("state", "unknown"),
|
|
284
|
+
started_at=status.get("started_at"),
|
|
285
|
+
finished_at=status.get("finished_at"),
|
|
286
|
+
n_agents=trace_summary.get("n_agents") or cfg.get("personas", 0) * cfg.get("replicas", 0),
|
|
287
|
+
n_questions=trace_summary.get("n_questions"),
|
|
288
|
+
ciq_score=trace_summary.get("ciq_score"),
|
|
289
|
+
eig_mean=trace_summary.get("eig_mean"),
|
|
290
|
+
model=cfg.get("model"),
|
|
291
|
+
enable_tools=cfg.get("enable_tools"),
|
|
292
|
+
deliberation_rounds=cfg.get("deliberation_rounds"),
|
|
293
|
+
no_llm=cfg.get("no_llm"),
|
|
294
|
+
error=status.get("error"),
|
|
295
|
+
topic=cfg.get("topic") or None,
|
|
296
|
+
questions_set=cfg.get("questions") or None,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def list_sims_for_backend(backend: Any, *, limit: int | None = None) -> list[SimSummary]:
|
|
301
|
+
"""Return SimSummary list from a StorageBackend (server tier).
|
|
302
|
+
|
|
303
|
+
Accepts any object that implements ``list_sim_ids() -> list[str]``
|
|
304
|
+
and ``read_file(sim_id, filename) -> bytes | None``. The local
|
|
305
|
+
disk-walk path (``list_sims()``) is unaffected.
|
|
306
|
+
"""
|
|
307
|
+
out: list[SimSummary] = []
|
|
308
|
+
for sim_id in backend.list_sim_ids():
|
|
309
|
+
try:
|
|
310
|
+
s = _summary_from_backend(backend, sim_id)
|
|
311
|
+
if s is not None:
|
|
312
|
+
out.append(s)
|
|
313
|
+
except Exception:
|
|
314
|
+
continue
|
|
315
|
+
if limit is not None and len(out) >= limit:
|
|
316
|
+
break
|
|
317
|
+
return out
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def append_stderr(sim_id: str, line: str) -> None:
|
|
321
|
+
p = sim_dir(sim_id) / "stderr.log"
|
|
322
|
+
with p.open("a") as f:
|
|
323
|
+
f.write(line)
|
|
324
|
+
if not line.endswith("\n"):
|
|
325
|
+
f.write("\n")
|