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,248 @@
1
+ """MCP server — JSON-RPC 2.0 dispatcher with CapToken-bound provenance.
2
+
3
+ Accepts a :class:`MCPTransport` and a tool registry view, dispatches
4
+ ``initialize`` / ``tools/list`` / ``tools/call`` requests, and enforces
5
+ the T1-03 provenance binding before invoking any tool handler.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import asyncio
11
+ import logging
12
+ from collections.abc import Awaitable, Callable
13
+ from dataclasses import dataclass, field
14
+ from typing import Any
15
+
16
+ from hypermind.mcp.provenance import (
17
+ ERR_PROVENANCE_MISSING,
18
+ ERR_PROVENANCE_UNKNOWN_CAP,
19
+ Provenance,
20
+ ProvenanceError,
21
+ compute_cap_token_root,
22
+ verify_provenance,
23
+ )
24
+ from hypermind.mcp.transport import MCP_SCHEMA_VERSION, MCPTransport
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ # JSON-RPC 2.0 standard error codes (§5.1).
30
+ ERR_PARSE = -32700
31
+ ERR_INVALID_REQUEST = -32600
32
+ ERR_METHOD_NOT_FOUND = -32601
33
+ ERR_INVALID_PARAMS = -32602
34
+ ERR_INTERNAL = -32603
35
+
36
+ #: Async handler signature: ``async def handler(params) -> result``.
37
+ ToolHandler = Callable[[dict[str, Any]], Awaitable[Any]]
38
+
39
+
40
+ @dataclass
41
+ class ToolDescriptor:
42
+ """An MCP-visible tool entry."""
43
+
44
+ name: str
45
+ description: str
46
+ parameters_schema: dict[str, Any]
47
+ handler: ToolHandler
48
+
49
+ def to_mcp_dict(self) -> dict[str, Any]:
50
+ return {
51
+ "name": self.name,
52
+ "description": self.description,
53
+ "inputSchema": self.parameters_schema,
54
+ }
55
+
56
+
57
+ @dataclass
58
+ class MCPServer:
59
+ """JSON-RPC 2.0 dispatcher serving a tool registry over an
60
+ :class:`MCPTransport`.
61
+
62
+ Provenance enforcement
63
+ ----------------------
64
+ Every ``tools/call`` request MUST carry a ``provenance`` block in
65
+ its ``params`` (signed Ed25519 over the canonical CBOR of the
66
+ request body — see :mod:`hypermind.mcp.provenance`). The server
67
+ rejects calls that:
68
+
69
+ 1. Omit the provenance block (``-32001``).
70
+ 2. Reference a ``cap_token_root`` not in :attr:`known_cap_roots`
71
+ (``-32003``).
72
+ 3. Have an invalid signature or mismatched ``request_kid``
73
+ (``-32002`` / ``-32004``).
74
+
75
+ Idempotency
76
+ -----------
77
+ A successful ``tools/call`` caches its result keyed by
78
+ ``request_kid``. Re-receiving the same ``request_kid`` returns the
79
+ cached result without re-dispatching the handler — closing T1-05's
80
+ "idempotent retry" requirement.
81
+ """
82
+
83
+ tools: dict[str, ToolDescriptor] = field(default_factory=dict)
84
+ #: SHA-256 hashes of root CapToken canonical bytes that are
85
+ #: authorised to call tools on this server. The server checks
86
+ #: membership only — full CapToken validation happens upstream
87
+ #: when the cap is registered.
88
+ known_cap_roots: set[bytes] = field(default_factory=set)
89
+ #: request_kid -> cached result (for idempotent retries).
90
+ _idempotency_cache: dict[bytes, Any] = field(default_factory=dict)
91
+
92
+ def register_tool(
93
+ self,
94
+ name: str,
95
+ *,
96
+ description: str,
97
+ parameters_schema: dict[str, Any],
98
+ handler: ToolHandler,
99
+ ) -> None:
100
+ if name in self.tools:
101
+ raise ValueError(f"tool {name!r} already registered")
102
+ self.tools[name] = ToolDescriptor(
103
+ name=name,
104
+ description=description,
105
+ parameters_schema=parameters_schema,
106
+ handler=handler,
107
+ )
108
+
109
+ def register_cap_root(self, cap_token_bytes: bytes) -> bytes:
110
+ """Register a CapToken root and return its SHA-256 root hash."""
111
+ root = compute_cap_token_root(cap_token_bytes)
112
+ self.known_cap_roots.add(root)
113
+ return root
114
+
115
+ # ---- request loop --------------------------------------------------
116
+
117
+ async def serve(self, transport: MCPTransport) -> None:
118
+ """Service requests until the transport is closed."""
119
+ try:
120
+ while True:
121
+ try:
122
+ msg = await transport.recv()
123
+ except (ConnectionError, asyncio.CancelledError):
124
+ return
125
+ except (ValueError, Exception) as e:
126
+ logger.warning("MCP recv error: %s", e)
127
+ await self._send_error(transport, None, ERR_PARSE, str(e))
128
+ continue
129
+ await self._dispatch(transport, msg)
130
+ finally:
131
+ await transport.close()
132
+
133
+ async def _dispatch(self, transport: MCPTransport, msg: dict[str, Any]) -> None:
134
+ if msg.get("jsonrpc") != "2.0":
135
+ await self._send_error(
136
+ transport,
137
+ msg.get("id"),
138
+ ERR_INVALID_REQUEST,
139
+ "jsonrpc field must be '2.0'",
140
+ )
141
+ return
142
+ msg_id = msg.get("id")
143
+ method = msg.get("method")
144
+ params = msg.get("params") or {}
145
+ if not isinstance(method, str):
146
+ await self._send_error(
147
+ transport, msg_id, ERR_INVALID_REQUEST, "method must be a string"
148
+ )
149
+ return
150
+ if not isinstance(params, dict):
151
+ await self._send_error(
152
+ transport, msg_id, ERR_INVALID_PARAMS, "params must be an object"
153
+ )
154
+ return
155
+ try:
156
+ result = await self._handle(method, params)
157
+ except ProvenanceError as e:
158
+ await self._send_error(transport, msg_id, e.code, e.message)
159
+ return
160
+ except _MethodNotFound as e:
161
+ await self._send_error(transport, msg_id, ERR_METHOD_NOT_FOUND, str(e))
162
+ return
163
+ except _InvalidParams as e:
164
+ await self._send_error(transport, msg_id, ERR_INVALID_PARAMS, str(e))
165
+ return
166
+ except Exception as e:
167
+ logger.exception("MCP handler raised")
168
+ await self._send_error(transport, msg_id, ERR_INTERNAL, str(e))
169
+ return
170
+ await transport.send({"jsonrpc": "2.0", "id": msg_id, "result": result})
171
+
172
+ async def _handle(self, method: str, params: dict[str, Any]) -> Any:
173
+ if method == "initialize":
174
+ return {
175
+ "protocolVersion": MCP_SCHEMA_VERSION,
176
+ "capabilities": {"tools": {"listChanged": False}},
177
+ "serverInfo": {"name": "hypermind-mcp", "version": "0.10.1"},
178
+ }
179
+ if method == "tools/list":
180
+ return {"tools": [t.to_mcp_dict() for t in self.tools.values()]}
181
+ if method == "tools/call":
182
+ return await self._handle_tool_call(params)
183
+ raise _MethodNotFound(f"unknown method {method!r}")
184
+
185
+ async def _handle_tool_call(self, params: dict[str, Any]) -> Any:
186
+ name = params.get("name")
187
+ if not isinstance(name, str):
188
+ raise _InvalidParams("tools/call requires 'name' (string)")
189
+ arguments = params.get("arguments") or {}
190
+ if not isinstance(arguments, dict):
191
+ raise _InvalidParams("tools/call 'arguments' must be an object")
192
+ tool = self.tools.get(name)
193
+ if tool is None:
194
+ raise _MethodNotFound(f"unknown tool {name!r}")
195
+
196
+ # ---- T1-03: provenance enforcement -----------------------------
197
+ prov_obj = params.get("provenance")
198
+ if prov_obj is None:
199
+ raise ProvenanceError(
200
+ ERR_PROVENANCE_MISSING,
201
+ "tools/call requires a 'provenance' object (T1-03)",
202
+ )
203
+ provenance = Provenance.from_dict(prov_obj)
204
+ # The signed body is the params *without* provenance — strip it
205
+ # before recomputing the request_kid.
206
+ signed_params = {k: v for k, v in params.items() if k != "provenance"}
207
+ verify_provenance(
208
+ method="tools/call",
209
+ params_without_provenance=signed_params,
210
+ provenance=provenance,
211
+ )
212
+ if provenance.cap_token_root not in self.known_cap_roots:
213
+ raise ProvenanceError(
214
+ ERR_PROVENANCE_UNKNOWN_CAP,
215
+ "cap_token_root is not registered with this server",
216
+ )
217
+
218
+ # ---- T1-05: idempotent retry -----------------------------------
219
+ cached = self._idempotency_cache.get(provenance.request_kid)
220
+ if cached is not None:
221
+ return cached
222
+ result = await tool.handler(arguments)
223
+ envelope = {"content": result, "request_kid": provenance.request_kid.hex()}
224
+ self._idempotency_cache[provenance.request_kid] = envelope
225
+ return envelope
226
+
227
+ @staticmethod
228
+ async def _send_error(
229
+ transport: MCPTransport,
230
+ msg_id: Any,
231
+ code: int,
232
+ message: str,
233
+ ) -> None:
234
+ await transport.send(
235
+ {
236
+ "jsonrpc": "2.0",
237
+ "id": msg_id,
238
+ "error": {"code": code, "message": message},
239
+ }
240
+ )
241
+
242
+
243
+ class _MethodNotFound(Exception):
244
+ pass
245
+
246
+
247
+ class _InvalidParams(Exception):
248
+ pass
@@ -0,0 +1,206 @@
1
+ """MCP transport layer — JSON-RPC 2.0 over stdio (and WebSocket, deferred).
2
+
3
+ Pinned MCP schema version: **MCP v1.0** (`MCP_SCHEMA_VERSION` constant
4
+ below). The JSON-RPC 2.0 envelope on the wire is::
5
+
6
+ {"jsonrpc": "2.0", "id": <int|str>, "method": <str>, "params": <obj>}
7
+
8
+ Replies::
9
+
10
+ {"jsonrpc": "2.0", "id": <id>, "result": <obj>}
11
+ {"jsonrpc": "2.0", "id": <id>, "error": {"code": <int>, "message": <str>}}
12
+
13
+ Two transports ship in v0.10.1:
14
+
15
+ - :class:`StdioTransport` — line-delimited JSON over a pair of byte
16
+ streams (asyncio :class:`asyncio.StreamReader`/:class:`StreamWriter`).
17
+ Suitable for in-process pipes, subprocess stdin/stdout, and Unix
18
+ domain sockets wrapped as streams.
19
+ - :class:`WebSocketTransport` — only available when the optional
20
+ ``websockets`` extra is installed. v0.10.1 ships the class but
21
+ declares WebSocket support as deferred to v0.10.2 if the dependency
22
+ is not present at import time. The class itself imports lazily.
23
+
24
+ Both transports implement the :class:`MCPTransport` Protocol.
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import asyncio
30
+ import json
31
+ from typing import Any, Protocol, runtime_checkable
32
+
33
+ #: MCP schema version this transport speaks. Bound into the
34
+ #: ``initialize`` handshake so peers can negotiate.
35
+ MCP_SCHEMA_VERSION = "1.0"
36
+
37
+
38
+ @runtime_checkable
39
+ class MCPTransport(Protocol):
40
+ """Bidirectional message channel for JSON-RPC 2.0 frames."""
41
+
42
+ async def send(self, msg: dict[str, Any]) -> None: ...
43
+
44
+ async def recv(self) -> dict[str, Any]: ...
45
+
46
+ async def close(self) -> None: ...
47
+
48
+
49
+ class StdioTransport:
50
+ """Line-delimited JSON over an asyncio reader/writer pair.
51
+
52
+ Each frame is a single JSON object terminated by ``\\n``. Empty
53
+ lines and lines that fail to decode are surfaced as
54
+ :class:`ValueError` from :meth:`recv` so the caller can decide
55
+ whether to skip or close the channel.
56
+
57
+ The transport accepts either a real :class:`asyncio.StreamWriter`
58
+ or a duck-typed object that exposes ``write(bytes)`` plus async
59
+ ``drain()`` / ``close()``. The in-memory test pair below uses the
60
+ duck-typed form to avoid the StreamReaderProtocol handshake that
61
+ asyncio's real StreamWriter needs.
62
+ """
63
+
64
+ def __init__(
65
+ self,
66
+ reader: asyncio.StreamReader,
67
+ writer: Any,
68
+ ) -> None:
69
+ self._reader = reader
70
+ self._writer = writer
71
+ self._send_lock = asyncio.Lock()
72
+ self._closed = False
73
+
74
+ async def send(self, msg: dict[str, Any]) -> None:
75
+ if self._closed:
76
+ raise ConnectionError("transport closed")
77
+ line = (json.dumps(msg, separators=(",", ":")) + "\n").encode("utf-8")
78
+ async with self._send_lock:
79
+ self._writer.write(line)
80
+ drain = getattr(self._writer, "drain", None)
81
+ if drain is not None:
82
+ result = drain()
83
+ if asyncio.iscoroutine(result):
84
+ await result
85
+
86
+ async def recv(self) -> dict[str, Any]:
87
+ if self._closed:
88
+ raise ConnectionError("transport closed")
89
+ line = await self._reader.readline()
90
+ if not line:
91
+ raise ConnectionError("peer closed transport")
92
+ try:
93
+ obj = json.loads(line.decode("utf-8").rstrip("\n"))
94
+ except (UnicodeDecodeError, json.JSONDecodeError) as e:
95
+ raise ValueError(f"non-JSON frame: {e}") from e
96
+ if not isinstance(obj, dict):
97
+ raise ValueError("JSON-RPC frame must be a JSON object")
98
+ return obj
99
+
100
+ async def close(self) -> None:
101
+ if self._closed:
102
+ return
103
+ self._closed = True
104
+ try:
105
+ close = getattr(self._writer, "close", None)
106
+ if close is not None:
107
+ result = close()
108
+ if asyncio.iscoroutine(result):
109
+ await result
110
+ wait_closed = getattr(self._writer, "wait_closed", None)
111
+ if wait_closed is not None:
112
+ result = wait_closed()
113
+ if asyncio.iscoroutine(result):
114
+ await result
115
+ except (ConnectionError, OSError):
116
+ pass
117
+
118
+ @classmethod
119
+ def in_memory_pair(cls) -> tuple[StdioTransport, StdioTransport]:
120
+ """Return two transports connected by a pair of in-memory pipes.
121
+
122
+ Useful for unit tests that want JSON-RPC over the real
123
+ :class:`StdioTransport` code path without spawning a subprocess.
124
+ """
125
+ a_reader: asyncio.StreamReader = asyncio.StreamReader()
126
+ b_reader: asyncio.StreamReader = asyncio.StreamReader()
127
+ # Side A reads from a_reader, writes feed b_reader (B's input).
128
+ side_a = cls(a_reader, _ReaderFeederWriter(b_reader))
129
+ side_b = cls(b_reader, _ReaderFeederWriter(a_reader))
130
+ return side_a, side_b
131
+
132
+
133
+ class _ReaderFeederWriter:
134
+ """Tiny duck-typed writer that pushes bytes into an asyncio
135
+ StreamReader on the peer side. Used by
136
+ :meth:`StdioTransport.in_memory_pair` to wire two transports
137
+ without spinning up a real protocol/transport pair."""
138
+
139
+ def __init__(self, peer_reader: asyncio.StreamReader) -> None:
140
+ self._peer_reader = peer_reader
141
+ self._closed = False
142
+
143
+ def write(self, data: bytes) -> None:
144
+ if self._closed:
145
+ raise ConnectionError("writer closed")
146
+ self._peer_reader.feed_data(data)
147
+
148
+ async def drain(self) -> None:
149
+ # In-memory: no buffer pressure, drain is a no-op.
150
+ return None
151
+
152
+ def close(self) -> None:
153
+ if self._closed:
154
+ return
155
+ self._closed = True
156
+ self._peer_reader.feed_eof()
157
+
158
+ async def wait_closed(self) -> None:
159
+ return None
160
+
161
+
162
+ # ---------------------------------------------------------------------------
163
+ # WebSocket transport — optional, deferred to v0.10.2 unless extra installed.
164
+ # ---------------------------------------------------------------------------
165
+
166
+
167
+ class WebSocketTransport:
168
+ """JSON-RPC 2.0 over a single WebSocket connection.
169
+
170
+ Requires the optional ``websockets`` dependency. v0.10.1 declares
171
+ this transport as a deferred capability — the class is importable
172
+ even without ``websockets`` installed, but instantiation raises
173
+ ``RuntimeError`` directing the caller to install the extra. The
174
+ full WebSocket implementation lands in v0.10.2.
175
+ """
176
+
177
+ def __init__(self, ws: Any) -> None:
178
+ try:
179
+ import websockets # noqa: F401
180
+ except ImportError as e: # pragma: no cover — env-dependent
181
+ raise RuntimeError(
182
+ "WebSocketTransport requires the `websockets` package "
183
+ "(deferred to v0.10.2; install via `pip install websockets`)"
184
+ ) from e
185
+ self._ws = ws
186
+ self._closed = False
187
+
188
+ async def send(self, msg: dict[str, Any]) -> None: # pragma: no cover
189
+ if self._closed:
190
+ raise ConnectionError("transport closed")
191
+ await self._ws.send(json.dumps(msg, separators=(",", ":")))
192
+
193
+ async def recv(self) -> dict[str, Any]: # pragma: no cover
194
+ raw = await self._ws.recv()
195
+ if isinstance(raw, bytes):
196
+ raw = raw.decode("utf-8")
197
+ obj = json.loads(raw)
198
+ if not isinstance(obj, dict):
199
+ raise ValueError("JSON-RPC frame must be a JSON object")
200
+ return obj
201
+
202
+ async def close(self) -> None: # pragma: no cover
203
+ if self._closed:
204
+ return
205
+ self._closed = True
206
+ await self._ws.close()
@@ -0,0 +1,150 @@
1
+ """hypermind.mind — the agent mind.
2
+
3
+ Adds a deliberation layer on top of HyperMindAgent's primitive verbs
4
+ (publish_claim, dispute, consult, synthesize, checkpoint). The mind is
5
+ strictly *additive*: agents constructed without a Deliberator behave
6
+ exactly as before. Calling ``agent.pursue(goal)`` activates the mind.
7
+
8
+ Public surface:
9
+ Goal, GoalQueue — what the agent wants to achieve
10
+ ActionPolicy + built-in policies — how the agent decides
11
+ Deliberator — the loop that turns goals into actions
12
+ SelfReflector — adjusts policy thresholds from track record
13
+ DiversityGuard — blocks correlated synthesis
14
+ SybilGuard — caps influence of new agents
15
+ LeverageCap — bounds claim impact by historical Brier
16
+
17
+ Every action dispatched by a Deliberator is gated by a CapToken (caveat
18
+ chain) — autonomy is governable, not unbounded.
19
+
20
+ Spec: docs/spec/19-autonomy-contract.md
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ from hypermind.mind.active import (
26
+ AgentSnapshot,
27
+ AntiHerdingTrigger,
28
+ CandidateQuestion,
29
+ CurriculumScheduler,
30
+ GradedQuestion,
31
+ InformationGainPlanner,
32
+ )
33
+ from hypermind.mind.belief import (
34
+ BeliefRecord,
35
+ BeliefRevisionError,
36
+ BeliefStore,
37
+ TheoryOfMind,
38
+ )
39
+ from hypermind.mind.deliberator import Deliberator, DeliberatorTick
40
+ from hypermind.mind.diversity import DiversityGuard, EpistemicDependence, QuorumResult
41
+ from hypermind.mind.evolve import BrierFeedback, MentorFollowing, SelfReflector
42
+ from hypermind.mind.federation import (
43
+ FederationPolicy,
44
+ assert_never_merge,
45
+ auto_relay,
46
+ )
47
+ from hypermind.mind.goals import Goal, GoalOutcome, GoalQueue, GoalStatus
48
+ from hypermind.mind.mediator import MediatorSelector, select_mediator
49
+ from hypermind.mind.policy import (
50
+ ActionDecision,
51
+ ActionKind,
52
+ ActionPolicy,
53
+ CheckpointOnSignalGain,
54
+ ConsultOnDisagreement,
55
+ DisputeOnContradiction,
56
+ LeverageCap,
57
+ NoOpPolicy,
58
+ PolicyContext,
59
+ PublishWhenConfident,
60
+ select_top_decision,
61
+ )
62
+ from hypermind.mind.records import (
63
+ DissentRecord,
64
+ IntentRecord,
65
+ TraceRecord,
66
+ decode_mind_record,
67
+ encode_mind_record,
68
+ )
69
+ from hypermind.mind.roles import (
70
+ Role,
71
+ RoleKind,
72
+ SwarmComposition,
73
+ generalist,
74
+ mediator,
75
+ oracle,
76
+ role_by_name,
77
+ sceptic,
78
+ scout,
79
+ synthesist,
80
+ )
81
+ from hypermind.mind.sybil_guard import SybilGuard
82
+ from hypermind.mind.world_model import (
83
+ BetaWorldModel,
84
+ ImaginedOutcome,
85
+ LookaheadPolicy,
86
+ WorldModel,
87
+ update_world_model_from_resolution,
88
+ )
89
+
90
+ __all__ = [
91
+ "ActionDecision",
92
+ "ActionKind",
93
+ "ActionPolicy",
94
+ "AgentSnapshot",
95
+ "AntiHerdingTrigger",
96
+ "BeliefRecord",
97
+ "BeliefRevisionError",
98
+ "BeliefStore",
99
+ "BetaWorldModel",
100
+ "BrierFeedback",
101
+ "CandidateQuestion",
102
+ "CheckpointOnSignalGain",
103
+ "ConsultOnDisagreement",
104
+ "CurriculumScheduler",
105
+ "Deliberator",
106
+ "DeliberatorTick",
107
+ "DisputeOnContradiction",
108
+ "DissentRecord",
109
+ "DiversityGuard",
110
+ "EpistemicDependence",
111
+ "FederationPolicy",
112
+ "Goal",
113
+ "GoalOutcome",
114
+ "GoalQueue",
115
+ "GoalStatus",
116
+ "GradedQuestion",
117
+ "ImaginedOutcome",
118
+ "InformationGainPlanner",
119
+ "IntentRecord",
120
+ "LeverageCap",
121
+ "LookaheadPolicy",
122
+ "MediatorSelector",
123
+ "MentorFollowing",
124
+ "NoOpPolicy",
125
+ "PolicyContext",
126
+ "PublishWhenConfident",
127
+ "QuorumResult",
128
+ "Role",
129
+ "RoleKind",
130
+ "SelfReflector",
131
+ "SwarmComposition",
132
+ "SybilGuard",
133
+ "TheoryOfMind",
134
+ "TraceRecord",
135
+ "WorldModel",
136
+ "assert_never_merge",
137
+ "auto_relay",
138
+ "decode_mind_record",
139
+ "encode_mind_record",
140
+ "generalist",
141
+ "mediator",
142
+ "oracle",
143
+ "role_by_name",
144
+ "sceptic",
145
+ "scout",
146
+ "select_mediator",
147
+ "select_top_decision",
148
+ "synthesist",
149
+ "update_world_model_from_resolution",
150
+ ]