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.
Files changed (181) hide show
  1. hmctl/__init__.py +7 -0
  2. hmctl/cli.py +947 -0
  3. hypermind/__init__.py +208 -0
  4. hypermind/_deprecations.py +108 -0
  5. hypermind/agent.py +2896 -0
  6. hypermind/agent_card.py +127 -0
  7. hypermind/api/__init__.py +32 -0
  8. hypermind/api/app.py +686 -0
  9. hypermind/capability.py +923 -0
  10. hypermind/consult.py +493 -0
  11. hypermind/consult_bias.py +112 -0
  12. hypermind/contract_net.py +219 -0
  13. hypermind/crypto/__init__.py +21 -0
  14. hypermind/crypto/cose_encrypt.py +126 -0
  15. hypermind/crypto/dkg.py +246 -0
  16. hypermind/crypto/domain.py +59 -0
  17. hypermind/crypto/frost.py +771 -0
  18. hypermind/crypto/hashing.py +38 -0
  19. hypermind/crypto/hlc.py +163 -0
  20. hypermind/crypto/hpke.py +277 -0
  21. hypermind/crypto/kms.py +196 -0
  22. hypermind/crypto/kms_backends/__init__.py +56 -0
  23. hypermind/crypto/kms_backends/aws.py +128 -0
  24. hypermind/crypto/kms_backends/azure.py +114 -0
  25. hypermind/crypto/kms_backends/gcp.py +97 -0
  26. hypermind/crypto/kms_backends/vault.py +120 -0
  27. hypermind/crypto/namespace_root.py +204 -0
  28. hypermind/crypto/pq.py +152 -0
  29. hypermind/crypto/room_epoch.py +97 -0
  30. hypermind/crypto/signing.py +245 -0
  31. hypermind/deliberation.py +355 -0
  32. hypermind/did_web.py +256 -0
  33. hypermind/dispute.py +858 -0
  34. hypermind/errors.py +218 -0
  35. hypermind/eval/__init__.py +49 -0
  36. hypermind/eval/calibration.py +307 -0
  37. hypermind/eval/comparison.py +251 -0
  38. hypermind/eval/replay.py +202 -0
  39. hypermind/eval/swarm_iq.py +718 -0
  40. hypermind/knowledge/__init__.py +17 -0
  41. hypermind/knowledge/citation_graph.py +141 -0
  42. hypermind/knowledge/correlated_agreement.py +167 -0
  43. hypermind/knowledge/embedding_registry.py +115 -0
  44. hypermind/knowledge/kernel_store.py +197 -0
  45. hypermind/knowledge/rekor.py +173 -0
  46. hypermind/knowledge/reputation.py +364 -0
  47. hypermind/knowledge/swarm_memory.py +523 -0
  48. hypermind/knowledge/transparency.py +409 -0
  49. hypermind/mcp/__init__.py +60 -0
  50. hypermind/mcp/bridge.py +151 -0
  51. hypermind/mcp/client.py +142 -0
  52. hypermind/mcp/provenance.py +214 -0
  53. hypermind/mcp/relata_tools.py +152 -0
  54. hypermind/mcp/server.py +248 -0
  55. hypermind/mcp/transport.py +206 -0
  56. hypermind/mind/__init__.py +150 -0
  57. hypermind/mind/active.py +234 -0
  58. hypermind/mind/belief.py +369 -0
  59. hypermind/mind/deliberator.py +625 -0
  60. hypermind/mind/diversity.py +144 -0
  61. hypermind/mind/evolve.py +184 -0
  62. hypermind/mind/federation.py +154 -0
  63. hypermind/mind/goals.py +117 -0
  64. hypermind/mind/integration.py +190 -0
  65. hypermind/mind/mediator.py +74 -0
  66. hypermind/mind/memory_store.py +129 -0
  67. hypermind/mind/policy.py +307 -0
  68. hypermind/mind/records.py +260 -0
  69. hypermind/mind/roles.py +190 -0
  70. hypermind/mind/sybil_guard.py +41 -0
  71. hypermind/mind/world_model.py +166 -0
  72. hypermind/namespace.py +515 -0
  73. hypermind/namespace_policy.py +254 -0
  74. hypermind/observability/__init__.py +25 -0
  75. hypermind/observability/asgi.py +214 -0
  76. hypermind/observability/cost.py +277 -0
  77. hypermind/observability/otel_export.py +200 -0
  78. hypermind/observability/recorder.py +344 -0
  79. hypermind/observability/swarm_trace.py +438 -0
  80. hypermind/pin_policy.py +116 -0
  81. hypermind/py.typed +0 -0
  82. hypermind/responders/__init__.py +87 -0
  83. hypermind/responders/anthropic.py +159 -0
  84. hypermind/responders/base.py +162 -0
  85. hypermind/responders/openai.py +199 -0
  86. hypermind/responders/router.py +254 -0
  87. hypermind/responders/structured.py +287 -0
  88. hypermind/responders/tools.py +444 -0
  89. hypermind/revocation.py +270 -0
  90. hypermind/rule_ids.py +135 -0
  91. hypermind/schemas/encrypted_statement.cddl +28 -0
  92. hypermind/schemas/namespace_accept.cddl +20 -0
  93. hypermind/schemas/namespace_create.cddl +25 -0
  94. hypermind/schemas/namespace_founding_attest.cddl +30 -0
  95. hypermind/schemas/namespace_invite.cddl +22 -0
  96. hypermind/schemas/wire-v0.1.cddl +73 -0
  97. hypermind/simlab/__init__.py +15 -0
  98. hypermind/simlab/_persona_registry.py +93 -0
  99. hypermind/simlab/_scenario_impl.py +2778 -0
  100. hypermind/simlab/app.py +2538 -0
  101. hypermind/simlab/backends/__init__.py +27 -0
  102. hypermind/simlab/backends/anchor.py +88 -0
  103. hypermind/simlab/backends/local.py +32 -0
  104. hypermind/simlab/backends/server.py +79 -0
  105. hypermind/simlab/cli_command.py +108 -0
  106. hypermind/simlab/config.py +106 -0
  107. hypermind/simlab/deployment.py +26 -0
  108. hypermind/simlab/knowledge.py +716 -0
  109. hypermind/simlab/learning.py +295 -0
  110. hypermind/simlab/modes/__init__.py +115 -0
  111. hypermind/simlab/modes/_eval_real_llm.py +373 -0
  112. hypermind/simlab/modes/aar.py +611 -0
  113. hypermind/simlab/modes/backtest.py +610 -0
  114. hypermind/simlab/modes/binary_forecast.py +69 -0
  115. hypermind/simlab/modes/conformance.py +1869 -0
  116. hypermind/simlab/modes/evaluation.py +2271 -0
  117. hypermind/simlab/modes/governance.py +648 -0
  118. hypermind/simlab/modes/redteam.py +534 -0
  119. hypermind/simlab/modes/tabletop.py +797 -0
  120. hypermind/simlab/modes/tournament.py +448 -0
  121. hypermind/simlab/modes/whatif.py +523 -0
  122. hypermind/simlab/namespace.py +125 -0
  123. hypermind/simlab/personas.py +477 -0
  124. hypermind/simlab/prompt_generator.py +172 -0
  125. hypermind/simlab/question_sets/geopolitics_q20.json +122 -0
  126. hypermind/simlab/question_sets/governance_q5.json +67 -0
  127. hypermind/simlab/question_sets/msft_outlook_q10.json +62 -0
  128. hypermind/simlab/question_sets/whatif_q3.json +38 -0
  129. hypermind/simlab/registry.py +325 -0
  130. hypermind/simlab/runner.py +239 -0
  131. hypermind/simlab/scenario.py +147 -0
  132. hypermind/simlab/swarms.py +180 -0
  133. hypermind/simlab/tool_handlers/__init__.py +4 -0
  134. hypermind/simlab/tool_handlers/alpha_vantage.py +39 -0
  135. hypermind/simlab/tool_handlers/brave.py +46 -0
  136. hypermind/simlab/tool_handlers/newsapi.py +50 -0
  137. hypermind/simlab/tool_handlers/openweather.py +46 -0
  138. hypermind/simlab/tool_handlers/pinecone.py +52 -0
  139. hypermind/simlab/tool_handlers/qdrant.py +57 -0
  140. hypermind/simlab/tool_handlers/query_knowledge_base.py +21 -0
  141. hypermind/simlab/tool_handlers/relata_recall.py +61 -0
  142. hypermind/simlab/tool_handlers/retrieve_document.py +21 -0
  143. hypermind/simlab/tool_handlers/serper.py +46 -0
  144. hypermind/simlab/tool_handlers/tavily.py +53 -0
  145. hypermind/simlab/tool_handlers/weaviate.py +65 -0
  146. hypermind/simlab/tool_handlers/wikipedia.py +64 -0
  147. hypermind/simlab/tool_handlers/wolfram.py +48 -0
  148. hypermind/simlab/tool_handlers/yahoo_finance.py +49 -0
  149. hypermind/simlab/tool_registry.py +568 -0
  150. hypermind/simlab/topic_adapter.py +338 -0
  151. hypermind/simlab/topic_questions.py +259 -0
  152. hypermind/storage/__init__.py +69 -0
  153. hypermind/storage/backend.py +71 -0
  154. hypermind/storage/relata.py +245 -0
  155. hypermind/storage/sqlite.py +258 -0
  156. hypermind/sync.py +191 -0
  157. hypermind/tal.py +175 -0
  158. hypermind/tasks.py +334 -0
  159. hypermind/testing.py +16 -0
  160. hypermind/tools/__init__.py +32 -0
  161. hypermind/tools/registry.py +61 -0
  162. hypermind/transport/__init__.py +22 -0
  163. hypermind/transport/anti_entropy.py +708 -0
  164. hypermind/transport/bus.py +206 -0
  165. hypermind/transport/knows_delta.py +204 -0
  166. hypermind/transport/libp2p.py +298 -0
  167. hypermind/transport/placement.py +58 -0
  168. hypermind/transport/tcp.py +797 -0
  169. hypermind/transport/tls_profile.py +417 -0
  170. hypermind/types.py +59 -0
  171. hypermind/uncertainty.py +222 -0
  172. hypermind/wire.py +897 -0
  173. hypermind/workflow/__init__.py +72 -0
  174. hypermind/workflow/engine.py +655 -0
  175. hypermind/workflow/plan.py +182 -0
  176. hypermind/workflow/repair.py +203 -0
  177. hypermind-0.11.0.dist-info/METADATA +524 -0
  178. hypermind-0.11.0.dist-info/RECORD +181 -0
  179. hypermind-0.11.0.dist-info/WHEEL +4 -0
  180. hypermind-0.11.0.dist-info/entry_points.txt +2 -0
  181. hypermind-0.11.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,344 @@
1
+ """Structured log recorder + Prometheus-shape counters.
2
+
3
+ Mandated log fields (spec/13-observability.md):
4
+ ts_hlc, ts_wall, namespace, agent_id, event_type, kid, trace_id,
5
+ span_id, parent_span_id, key_fingerprint, sig_alg, peer_id, severity
6
+
7
+ Mandated counters:
8
+ hypermind_seal_total{namespace, topic, result}
9
+ hypermind_dispute_open_total
10
+ hypermind_errors_total{class}
11
+ ...
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import asyncio
17
+ import threading
18
+ import time
19
+ import uuid
20
+ from collections import Counter, deque
21
+ from collections.abc import AsyncIterator
22
+ from dataclasses import dataclass, field
23
+ from typing import Any
24
+
25
+ DEFAULT_MAX_RECORDS = 10_000
26
+
27
+
28
+ @dataclass
29
+ class LogRecord:
30
+ ts_wall_ms: int
31
+ ts_hlc: str
32
+ namespace: str
33
+ agent_id: str
34
+ event_type: str
35
+ severity: str
36
+ fields: dict[str, Any] = field(default_factory=dict)
37
+ namespace_id: str | None = None
38
+
39
+
40
+ class Recorder:
41
+ """Thread-safe in-process recorder used by tests + by the runtime.
42
+
43
+ Records are kept in a bounded ring buffer (``maxlen=max_records``) so
44
+ long-running processes cannot leak memory through the global recorder.
45
+ When the buffer is full, the oldest record is evicted and
46
+ ``records_dropped_total`` is incremented.
47
+ """
48
+
49
+ def __init__(
50
+ self,
51
+ max_records: int = DEFAULT_MAX_RECORDS,
52
+ *,
53
+ namespace_id: str | None = None,
54
+ parent: Recorder | None = None,
55
+ ) -> None:
56
+ self._max_records = max_records
57
+ self.records: deque[LogRecord] = deque(maxlen=max_records)
58
+ self.counters: Counter[str] = Counter()
59
+ self.records_dropped_total: int = 0
60
+ self.namespace_id: str | None = namespace_id
61
+ # WS-G v0.7: scoped recorders also fan out to ``parent`` (the
62
+ # process-global recorder) so operators / tests reading the
63
+ # global view still see all activity. Parent never points back.
64
+ self._parent: Recorder | None = parent
65
+ self._lock = threading.Lock()
66
+ # v0.6.1 — tail() consumers. Each consumer gets its own asyncio.Event
67
+ # set on every emit() so they can wake up and read new records.
68
+ # We don't keep per-consumer indexes here; tail() owns the head pointer.
69
+ self._tail_consumers: list[asyncio.Event] = []
70
+
71
+ @classmethod
72
+ def scoped(
73
+ cls,
74
+ namespace_id: str,
75
+ *,
76
+ max_records: int = DEFAULT_MAX_RECORDS,
77
+ parent: Recorder | None = None,
78
+ ) -> Recorder:
79
+ """Return a fresh Recorder tagged with the given namespace.
80
+
81
+ v0.7 wires per-``_NamespaceWorld`` recorders through this
82
+ factory. ``parent`` defaults to the process-global recorder so
83
+ the global view continues to receive every emission for
84
+ operator/test compatibility.
85
+ """
86
+ if parent is None:
87
+ parent = _GLOBAL
88
+ return cls(max_records=max_records, namespace_id=namespace_id, parent=parent)
89
+
90
+ def emit(
91
+ self,
92
+ *,
93
+ namespace: str,
94
+ agent_id: str,
95
+ event_type: str,
96
+ severity: str = "info",
97
+ ts_hlc: str = "",
98
+ **fields: Any,
99
+ ) -> None:
100
+ record = LogRecord(
101
+ ts_wall_ms=int(time.time() * 1000),
102
+ ts_hlc=ts_hlc,
103
+ namespace=namespace,
104
+ agent_id=agent_id,
105
+ event_type=event_type,
106
+ severity=severity,
107
+ fields=dict(fields),
108
+ namespace_id=self.namespace_id,
109
+ )
110
+ with self._lock:
111
+ if len(self.records) == self._max_records:
112
+ # deque is full; appending will evict the oldest record.
113
+ self.records_dropped_total += 1
114
+ self.records.append(record)
115
+ consumers = list(self._tail_consumers)
116
+ # Wake any tail() consumers (best-effort; we never block emit()).
117
+ for ev in consumers:
118
+ try:
119
+ ev.set()
120
+ except RuntimeError: # consumer's loop closed; pruned next tail()
121
+ pass
122
+ # Fan-out to parent (e.g. process-global) for unified visibility.
123
+ if self._parent is not None:
124
+ self._parent.emit(
125
+ namespace=namespace,
126
+ agent_id=agent_id,
127
+ event_type=event_type,
128
+ severity=severity,
129
+ ts_hlc=ts_hlc,
130
+ **fields,
131
+ )
132
+
133
+ async def tail(self, *, replay: bool = True) -> AsyncIterator[LogRecord]:
134
+ """Async generator: yields new LogRecords as they arrive.
135
+
136
+ ``replay=True`` (default) yields records that were already in the
137
+ buffer before the consumer subscribed. Set ``replay=False`` to
138
+ only see records emitted after subscription.
139
+
140
+ Each consumer gets its own asyncio.Event; emit() sets all
141
+ consumer events. Cancellation-safe.
142
+ """
143
+ ev = asyncio.Event()
144
+ with self._lock:
145
+ self._tail_consumers.append(ev)
146
+ head = 0 if replay else len(self.records)
147
+ try:
148
+ while True:
149
+ with self._lock:
150
+ snapshot = list(self.records)
151
+ # The deque may have evicted entries below our head.
152
+ # Skip ahead to the current size if we've fallen behind.
153
+ if head > len(snapshot):
154
+ head = len(snapshot)
155
+ while head < len(snapshot):
156
+ yield snapshot[head]
157
+ head += 1
158
+ # Wait for the next emit().
159
+ ev.clear()
160
+ await ev.wait()
161
+ finally:
162
+ with self._lock:
163
+ try:
164
+ self._tail_consumers.remove(ev)
165
+ except ValueError:
166
+ pass
167
+
168
+ def incr(self, metric: str, *, by: float = 1.0, **labels: Any) -> None:
169
+ key = metric
170
+ if labels:
171
+ label_str = ",".join(f"{k}={v}" for k, v in sorted(labels.items()))
172
+ key = f"{metric}{{{label_str}}}"
173
+ with self._lock:
174
+ self.counters[key] += by
175
+ if self._parent is not None:
176
+ self._parent.incr(metric, by=by, **labels)
177
+
178
+ def trace_id(self) -> str:
179
+ return uuid.uuid4().hex
180
+
181
+ def filter(self, *, event_type: str | None = None) -> list[LogRecord]:
182
+ with self._lock:
183
+ return [r for r in self.records if event_type is None or r.event_type == event_type]
184
+
185
+ def get_metrics(self) -> dict[str, Any]:
186
+ """Snapshot of recorder-internal metrics.
187
+
188
+ Exposes ``records_dropped_total`` so operators can detect when the
189
+ bounded ring buffer is dropping log lines.
190
+ """
191
+ with self._lock:
192
+ return {
193
+ "records_total": len(self.records),
194
+ "records_max": self._max_records,
195
+ "records_dropped_total": self.records_dropped_total,
196
+ "counters": dict(self.counters),
197
+ "namespace_id": self.namespace_id,
198
+ }
199
+
200
+ def reset(self) -> None:
201
+ with self._lock:
202
+ # ``deque.clear()`` preserves the maxlen invariant.
203
+ self.records.clear()
204
+ self.counters.clear()
205
+ self.records_dropped_total = 0
206
+
207
+ # ------------------------------------------------------------------ #
208
+ # OpenTelemetry integration (v0.8 production profile).
209
+ #
210
+ # The wrapped Recorder transparently forwards every public verb to
211
+ # the underlying instance and, for the agent verbs the spec calls
212
+ # out (``publish_claim``, ``consult``, ``dispute``, ``synthesize``,
213
+ # ``oracle_resolve``), also opens a span on the supplied tracer.
214
+ #
215
+ # If ``opentelemetry`` is not installed we emit a warning via the
216
+ # standard ``warnings`` module and return ``self`` unchanged so
217
+ # callers don't have to special-case the import path.
218
+ # ------------------------------------------------------------------ #
219
+ def with_otel(self, tracer_provider: Any | None = None) -> Recorder:
220
+ """Wrap this Recorder so agent verbs emit OTel spans.
221
+
222
+ ``tracer_provider`` is an ``opentelemetry.trace.TracerProvider``
223
+ instance (typed as ``Any`` here so we can keep the OTel import
224
+ optional). If None, the global provider is used.
225
+ """
226
+ try:
227
+ from opentelemetry import trace as _otel_trace # noqa: F401
228
+ except ImportError:
229
+ import warnings
230
+
231
+ warnings.warn(
232
+ "with_otel: opentelemetry not installed; "
233
+ "install hypermind[otel] to enable span emission. "
234
+ "Returning the un-wrapped Recorder.",
235
+ RuntimeWarning,
236
+ stacklevel=2,
237
+ )
238
+ return self
239
+
240
+ return _OtelRecorder(self, tracer_provider)
241
+
242
+
243
+ _GLOBAL = Recorder()
244
+
245
+
246
+ # OTel verbs the spec mandates spans for. Kept module-level so it's
247
+ # easy to extend without touching the wrapper class.
248
+ _OTEL_TRACED_EVENTS: frozenset[str] = frozenset(
249
+ {"publish_claim", "consult", "dispute", "synthesize", "oracle_resolve"}
250
+ )
251
+
252
+
253
+ class _OtelRecorder(Recorder):
254
+ """Recorder facade that forwards to the wrapped instance and emits
255
+ OTel spans for traced agent verbs.
256
+
257
+ The wrapper subclasses ``Recorder`` so it satisfies the same
258
+ structural contract used everywhere else in the SDK; the parent
259
+ state (``records``, ``counters``, etc.) is intentionally unused —
260
+ every public method delegates to ``_inner``.
261
+ """
262
+
263
+ def __init__(self, inner: Recorder, tracer_provider: Any | None) -> None:
264
+ # Bypass Recorder.__init__ — we do not want a second buffer.
265
+ # We still need a lock placeholder so the parent class's
266
+ # ``__repr__`` (if any) doesn't blow up.
267
+ self._inner = inner
268
+ from opentelemetry import trace as _otel_trace
269
+
270
+ if tracer_provider is None:
271
+ self._tracer = _otel_trace.get_tracer("hypermind")
272
+ else:
273
+ self._tracer = tracer_provider.get_tracer("hypermind")
274
+
275
+ # ---- delegation -------------------------------------------------- #
276
+ @property
277
+ def records(self) -> deque[LogRecord]: # type: ignore[override]
278
+ return self._inner.records
279
+
280
+ @property
281
+ def counters(self) -> Counter[str]: # type: ignore[override]
282
+ return self._inner.counters
283
+
284
+ @property
285
+ def records_dropped_total(self) -> int: # type: ignore[override]
286
+ return self._inner.records_dropped_total
287
+
288
+ @property
289
+ def namespace_id(self) -> str | None: # type: ignore[override]
290
+ return self._inner.namespace_id
291
+
292
+ def emit(
293
+ self,
294
+ *,
295
+ namespace: str,
296
+ agent_id: str,
297
+ event_type: str,
298
+ severity: str = "info",
299
+ ts_hlc: str = "",
300
+ **fields: Any,
301
+ ) -> None:
302
+ if event_type in _OTEL_TRACED_EVENTS:
303
+ with self._tracer.start_as_current_span(f"hypermind.{event_type}") as span:
304
+ span.set_attribute("hypermind.namespace", namespace)
305
+ span.set_attribute("hypermind.agent_id", agent_id)
306
+ span.set_attribute("hypermind.severity", severity)
307
+ if ts_hlc:
308
+ span.set_attribute("hypermind.hlc", ts_hlc)
309
+ self._inner.emit(
310
+ namespace=namespace,
311
+ agent_id=agent_id,
312
+ event_type=event_type,
313
+ severity=severity,
314
+ ts_hlc=ts_hlc,
315
+ **fields,
316
+ )
317
+ else:
318
+ self._inner.emit(
319
+ namespace=namespace,
320
+ agent_id=agent_id,
321
+ event_type=event_type,
322
+ severity=severity,
323
+ ts_hlc=ts_hlc,
324
+ **fields,
325
+ )
326
+
327
+ def incr(self, metric: str, *, by: float = 1.0, **labels: Any) -> None:
328
+ self._inner.incr(metric, by=by, **labels)
329
+
330
+ def trace_id(self) -> str:
331
+ return self._inner.trace_id()
332
+
333
+ def filter(self, *, event_type: str | None = None) -> list[LogRecord]:
334
+ return self._inner.filter(event_type=event_type)
335
+
336
+ def get_metrics(self) -> dict[str, Any]:
337
+ return self._inner.get_metrics()
338
+
339
+ def reset(self) -> None:
340
+ self._inner.reset()
341
+
342
+
343
+ def get_recorder() -> Recorder:
344
+ return _GLOBAL