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,260 @@
|
|
|
1
|
+
"""Mind collaboration records — intent, trace, dissent.
|
|
2
|
+
|
|
3
|
+
These are *application-layer* records carried inside the existing
|
|
4
|
+
``SEAL`` SignedStatement payload. We deliberately avoid extending the
|
|
5
|
+
COSE/CBOR wire format (which would require an envelope_schema_hash bump,
|
|
6
|
+
v0.2 wire-version negotiation, reference_verifier sync, and updated
|
|
7
|
+
Appendix Z conformance vectors).
|
|
8
|
+
|
|
9
|
+
Carrying mind-records as typed payloads keeps:
|
|
10
|
+
- A-ALG-BOUND intact (record_type stays "SEAL", every discriminator
|
|
11
|
+
in the protected header)
|
|
12
|
+
- reference_verifier interop intact
|
|
13
|
+
- transparency-log auditability intact (each is sealed and cosigned
|
|
14
|
+
via TransparencyService.append_and_cosign)
|
|
15
|
+
- reputation accountability intact (signed by issuer, citeable like
|
|
16
|
+
any other claim)
|
|
17
|
+
|
|
18
|
+
A future v1 wire-version may promote these to first-class record_types;
|
|
19
|
+
the on-disk codec defined here will be the canonical encoding.
|
|
20
|
+
|
|
21
|
+
Schema (CBOR map, deterministic):
|
|
22
|
+
{ 0: <kind str>, 1: <topic str>, 2: <body map>, 3: <hlc_ms int>,
|
|
23
|
+
4: <issuer_kid bytes>, 5: <version int> }
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import hashlib
|
|
29
|
+
from dataclasses import dataclass, field
|
|
30
|
+
from typing import Any, Literal
|
|
31
|
+
|
|
32
|
+
import cbor2
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def make_target_kid_commitment(target_kid: bytes) -> bytes:
|
|
36
|
+
"""P3-02: Compute the SHA-256 commitment over a target kid.
|
|
37
|
+
|
|
38
|
+
Use this to populate ``IntentRecord.target_kid_commitment`` when
|
|
39
|
+
you want to announce a dispute intent without revealing the full
|
|
40
|
+
target kid on the gossip bus.
|
|
41
|
+
"""
|
|
42
|
+
return hashlib.sha256(target_kid).digest()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
MIND_RECORD_VERSION = 1
|
|
46
|
+
MindRecordKind = Literal["intent", "trace", "dissent"]
|
|
47
|
+
|
|
48
|
+
# DoS gate: oversize CBOR payloads are rejected before parsing. Mind records
|
|
49
|
+
# are small typed envelopes; 64 KiB is comfortable headroom over the largest
|
|
50
|
+
# realistic intent/trace/dissent body.
|
|
51
|
+
MAX_MIND_RECORD_BYTES = 64 * 1024
|
|
52
|
+
|
|
53
|
+
_KEY_KIND = 0
|
|
54
|
+
_KEY_TOPIC = 1
|
|
55
|
+
_KEY_BODY = 2
|
|
56
|
+
_KEY_HLC_MS = 3
|
|
57
|
+
_KEY_ISSUER_KID = 4
|
|
58
|
+
_KEY_VERSION = 5
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass(frozen=True)
|
|
62
|
+
class IntentRecord:
|
|
63
|
+
"""Agent announces an upcoming action so peers can pre-stage / dedupe."""
|
|
64
|
+
|
|
65
|
+
topic: str
|
|
66
|
+
action: str # one of "consult", "publish", "dispute", ...
|
|
67
|
+
target_kid: bytes | None = None # for dispute intents
|
|
68
|
+
expected_completion_ms: int | None = None
|
|
69
|
+
note: str = ""
|
|
70
|
+
# P3-02: privacy-preserving commitment to the target kid.
|
|
71
|
+
# When set, this is SHA-256(target_kid) so peers can match dispute
|
|
72
|
+
# intents against claims they know without exposing the full kid to
|
|
73
|
+
# agents that don't need it. If both target_kid and
|
|
74
|
+
# target_kid_commitment are set, target_kid takes precedence for
|
|
75
|
+
# local use; target_kid_commitment is what is broadcast on the wire.
|
|
76
|
+
target_kid_commitment: bytes | None = None
|
|
77
|
+
|
|
78
|
+
kind: str = field(default="intent", init=False)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass(frozen=True)
|
|
82
|
+
class TraceRecord:
|
|
83
|
+
"""Reasoning trace for a published claim — the citations that shaped it.
|
|
84
|
+
|
|
85
|
+
First-class on-wire: makes the citation graph queryable from any
|
|
86
|
+
transparency-log audit. Closes failure-modes.md:49 (citation-graph
|
|
87
|
+
attack — citations were previously local-store only).
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
topic: str
|
|
91
|
+
claim_kid: bytes # the claim this trace explains
|
|
92
|
+
parent_kids: tuple[bytes, ...]
|
|
93
|
+
consult_kids: tuple[bytes, ...] = ()
|
|
94
|
+
note: str = ""
|
|
95
|
+
|
|
96
|
+
kind: str = field(default="trace", init=False)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@dataclass(frozen=True)
|
|
100
|
+
class DissentRecord:
|
|
101
|
+
"""Lightweight pre-dispute signal: 'I see this differently'.
|
|
102
|
+
|
|
103
|
+
No bond posted, no FSM transition. Surfaces minority disagreement
|
|
104
|
+
before it escalates to a full bonded dispute — closes the herding
|
|
105
|
+
failure mode (failure-modes.md:17) by making divergence visible
|
|
106
|
+
early. Carries an optional reasoning note so peers can adjudicate
|
|
107
|
+
whether to weight it.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
topic: str
|
|
111
|
+
target_kid: bytes
|
|
112
|
+
own_posterior: float
|
|
113
|
+
note: str = ""
|
|
114
|
+
margin: float = 0.0 # |own_posterior - peer_posterior|
|
|
115
|
+
|
|
116
|
+
kind: str = field(default="dissent", init=False)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# -----------------------------------------------------------------------
|
|
120
|
+
# Encoding
|
|
121
|
+
# -----------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def encode_mind_record(
|
|
125
|
+
record: IntentRecord | TraceRecord | DissentRecord,
|
|
126
|
+
*,
|
|
127
|
+
issuer_kid: bytes,
|
|
128
|
+
hlc_ms: int,
|
|
129
|
+
) -> bytes:
|
|
130
|
+
"""Serialise a mind record as deterministic CBOR for SEAL payload."""
|
|
131
|
+
if isinstance(record, IntentRecord):
|
|
132
|
+
body: dict[str, Any] = {
|
|
133
|
+
"action": record.action,
|
|
134
|
+
"note": record.note,
|
|
135
|
+
}
|
|
136
|
+
if record.target_kid is not None:
|
|
137
|
+
body["target_kid"] = record.target_kid
|
|
138
|
+
if record.expected_completion_ms is not None:
|
|
139
|
+
body["expected_completion_ms"] = record.expected_completion_ms
|
|
140
|
+
# P3-02: include the commitment if present
|
|
141
|
+
if record.target_kid_commitment is not None:
|
|
142
|
+
body["target_kid_commitment"] = record.target_kid_commitment
|
|
143
|
+
elif isinstance(record, TraceRecord):
|
|
144
|
+
body = {
|
|
145
|
+
"claim_kid": record.claim_kid,
|
|
146
|
+
"parent_kids": list(record.parent_kids),
|
|
147
|
+
"consult_kids": list(record.consult_kids),
|
|
148
|
+
"note": record.note,
|
|
149
|
+
}
|
|
150
|
+
elif isinstance(record, DissentRecord):
|
|
151
|
+
body = {
|
|
152
|
+
"target_kid": record.target_kid,
|
|
153
|
+
"own_posterior": record.own_posterior,
|
|
154
|
+
"margin": record.margin,
|
|
155
|
+
"note": record.note,
|
|
156
|
+
}
|
|
157
|
+
else: # pragma: no cover - typeguard
|
|
158
|
+
raise TypeError(f"unknown mind record type: {type(record).__name__}")
|
|
159
|
+
|
|
160
|
+
envelope = {
|
|
161
|
+
_KEY_KIND: record.kind,
|
|
162
|
+
_KEY_TOPIC: record.topic,
|
|
163
|
+
_KEY_BODY: body,
|
|
164
|
+
_KEY_HLC_MS: int(hlc_ms),
|
|
165
|
+
_KEY_ISSUER_KID: bytes(issuer_kid),
|
|
166
|
+
_KEY_VERSION: MIND_RECORD_VERSION,
|
|
167
|
+
}
|
|
168
|
+
return cbor2.dumps(envelope, canonical=True)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def decode_mind_record(
|
|
172
|
+
raw: bytes,
|
|
173
|
+
*,
|
|
174
|
+
expected_signer_kid: bytes,
|
|
175
|
+
) -> tuple[IntentRecord | TraceRecord | DissentRecord, bytes, int]:
|
|
176
|
+
"""Decode a mind-record payload. Returns (record, issuer_kid, hlc_ms).
|
|
177
|
+
|
|
178
|
+
The ``expected_signer_kid`` is the kid of the outer SignedStatement
|
|
179
|
+
signer; it MUST match the embedded ``issuer_kid`` to prevent a kid
|
|
180
|
+
spoofing attack where a valid signer wraps a payload that claims a
|
|
181
|
+
different agent as issuer.
|
|
182
|
+
|
|
183
|
+
Raises ``ValueError`` on malformed/unsupported records, oversize
|
|
184
|
+
payloads, or issuer_kid mismatch.
|
|
185
|
+
"""
|
|
186
|
+
if len(raw) > MAX_MIND_RECORD_BYTES:
|
|
187
|
+
raise ValueError(f"mind record too large: {len(raw)} > {MAX_MIND_RECORD_BYTES}")
|
|
188
|
+
try:
|
|
189
|
+
env = cbor2.loads(raw)
|
|
190
|
+
except Exception as e:
|
|
191
|
+
raise ValueError(f"mind record decode: not CBOR: {e}") from e
|
|
192
|
+
if not isinstance(env, dict):
|
|
193
|
+
raise ValueError("mind record decode: not a map")
|
|
194
|
+
version = env.get(_KEY_VERSION)
|
|
195
|
+
if version != MIND_RECORD_VERSION:
|
|
196
|
+
raise ValueError(f"mind record decode: unsupported version {version!r}")
|
|
197
|
+
kind = env.get(_KEY_KIND)
|
|
198
|
+
topic = env.get(_KEY_TOPIC)
|
|
199
|
+
body = env.get(_KEY_BODY) or {}
|
|
200
|
+
hlc_ms = int(env.get(_KEY_HLC_MS, 0))
|
|
201
|
+
issuer_kid = env.get(_KEY_ISSUER_KID, b"")
|
|
202
|
+
if not isinstance(topic, str) or not isinstance(body, dict):
|
|
203
|
+
raise ValueError("mind record decode: malformed topic/body")
|
|
204
|
+
if not isinstance(issuer_kid, (bytes, bytearray)):
|
|
205
|
+
raise ValueError("mind record decode: issuer_kid must be bytes")
|
|
206
|
+
issuer_kid_bytes = bytes(issuer_kid)
|
|
207
|
+
if issuer_kid_bytes != bytes(expected_signer_kid):
|
|
208
|
+
raise ValueError(
|
|
209
|
+
"issuer_kid mismatch: payload claims "
|
|
210
|
+
f"{issuer_kid_bytes[:8].hex()}, signer is "
|
|
211
|
+
f"{bytes(expected_signer_kid)[:8].hex()}"
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
if kind == "intent":
|
|
215
|
+
raw_commitment = body.get("target_kid_commitment")
|
|
216
|
+
rec: Any = IntentRecord(
|
|
217
|
+
topic=topic,
|
|
218
|
+
action=str(body.get("action", "")),
|
|
219
|
+
target_kid=body.get("target_kid"),
|
|
220
|
+
expected_completion_ms=body.get("expected_completion_ms"),
|
|
221
|
+
note=str(body.get("note", "")),
|
|
222
|
+
# P3-02: decode target_kid_commitment if present
|
|
223
|
+
target_kid_commitment=(
|
|
224
|
+
bytes(raw_commitment) if isinstance(raw_commitment, (bytes, bytearray)) else None
|
|
225
|
+
),
|
|
226
|
+
)
|
|
227
|
+
elif kind == "trace":
|
|
228
|
+
rec = TraceRecord(
|
|
229
|
+
topic=topic,
|
|
230
|
+
claim_kid=bytes(body.get("claim_kid", b"")),
|
|
231
|
+
parent_kids=tuple(bytes(p) for p in body.get("parent_kids", ())),
|
|
232
|
+
consult_kids=tuple(bytes(c) for c in body.get("consult_kids", ())),
|
|
233
|
+
note=str(body.get("note", "")),
|
|
234
|
+
)
|
|
235
|
+
elif kind == "dissent":
|
|
236
|
+
rec = DissentRecord(
|
|
237
|
+
topic=topic,
|
|
238
|
+
target_kid=bytes(body.get("target_kid", b"")),
|
|
239
|
+
own_posterior=float(body.get("own_posterior", 0.0)),
|
|
240
|
+
margin=float(body.get("margin", 0.0)),
|
|
241
|
+
note=str(body.get("note", "")),
|
|
242
|
+
)
|
|
243
|
+
else:
|
|
244
|
+
raise ValueError(f"mind record decode: unknown kind {kind!r}")
|
|
245
|
+
return rec, issuer_kid_bytes, hlc_ms
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def is_mind_record(raw: bytes) -> bool:
|
|
249
|
+
"""Cheap discriminator — does this look like a mind record envelope?"""
|
|
250
|
+
if len(raw) > MAX_MIND_RECORD_BYTES:
|
|
251
|
+
return False
|
|
252
|
+
try:
|
|
253
|
+
env = cbor2.loads(raw)
|
|
254
|
+
except Exception:
|
|
255
|
+
return False
|
|
256
|
+
return (
|
|
257
|
+
isinstance(env, dict)
|
|
258
|
+
and env.get(_KEY_VERSION) == MIND_RECORD_VERSION
|
|
259
|
+
and env.get(_KEY_KIND) in ("intent", "trace", "dissent")
|
|
260
|
+
)
|
hypermind/mind/roles.py
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""Role specialisation — Scout, Sceptic, Generalist, Synthesist, Mediator, Oracle.
|
|
2
|
+
|
|
3
|
+
A Role is a declarative bundle of (policies, caveats) that composes
|
|
4
|
+
with the existing CapToken chain. Picking a role for an agent is the
|
|
5
|
+
fastest path to a swarm of differentiated specialists rather than a
|
|
6
|
+
swarm of clones.
|
|
7
|
+
|
|
8
|
+
Spec: docs/spec/20-swarm-intelligence.md §20.2.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from enum import StrEnum
|
|
15
|
+
|
|
16
|
+
from hypermind.capability import Caveat
|
|
17
|
+
from hypermind.mind.policy import (
|
|
18
|
+
ActionPolicy,
|
|
19
|
+
CheckpointOnSignalGain,
|
|
20
|
+
ConsultOnDisagreement,
|
|
21
|
+
DisputeOnContradiction,
|
|
22
|
+
PublishWhenConfident,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class RoleKind(StrEnum):
|
|
27
|
+
SCOUT = "scout"
|
|
28
|
+
SCEPTIC = "sceptic"
|
|
29
|
+
GENERALIST = "generalist"
|
|
30
|
+
SYNTHESIST = "synthesist"
|
|
31
|
+
MEDIATOR = "mediator"
|
|
32
|
+
ORACLE = "oracle"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class Role:
|
|
37
|
+
"""A role bundle — policies + caveats + descriptive metadata."""
|
|
38
|
+
|
|
39
|
+
kind: RoleKind
|
|
40
|
+
policies: tuple[ActionPolicy, ...]
|
|
41
|
+
caveats: tuple[Caveat, ...] = ()
|
|
42
|
+
description: str = ""
|
|
43
|
+
publishes_primary: bool = True # False for Mediator/Oracle (they don't publish raw claims)
|
|
44
|
+
|
|
45
|
+
def policy_list(self) -> list[ActionPolicy]:
|
|
46
|
+
return list(self.policies)
|
|
47
|
+
|
|
48
|
+
def caveat_list(self) -> list[Caveat]:
|
|
49
|
+
return list(self.caveats)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# ---------------------------------------------------------------------------
|
|
53
|
+
# Built-in roles
|
|
54
|
+
# ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def scout(*, topic: str = "*") -> Role:
|
|
58
|
+
"""Low-confidence threshold, high consult frequency, broad caveat."""
|
|
59
|
+
return Role(
|
|
60
|
+
kind=RoleKind.SCOUT,
|
|
61
|
+
policies=(
|
|
62
|
+
PublishWhenConfident(threshold=0.65, priority=40),
|
|
63
|
+
ConsultOnDisagreement(divergence=0.15, priority=55),
|
|
64
|
+
CheckpointOnSignalGain(kl_threshold=0.05, priority=30),
|
|
65
|
+
),
|
|
66
|
+
caveats=(Caveat.topic(topic), Caveat.rate("60/min")),
|
|
67
|
+
description="early-warning, broad coverage, frequent consults",
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def sceptic(*, topic: str = "*") -> Role:
|
|
72
|
+
"""Aggressive disputes, high publish bar."""
|
|
73
|
+
return Role(
|
|
74
|
+
kind=RoleKind.SCEPTIC,
|
|
75
|
+
policies=(
|
|
76
|
+
PublishWhenConfident(threshold=0.92, priority=30),
|
|
77
|
+
DisputeOnContradiction(min_gap=0.3, min_rep_delta=0.10, priority=70),
|
|
78
|
+
ConsultOnDisagreement(divergence=0.25, priority=40),
|
|
79
|
+
),
|
|
80
|
+
caveats=(Caveat.topic(topic), Caveat.rate("30/min")),
|
|
81
|
+
description="contrarian; raises the bar on consensus",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def generalist(*, topic: str = "*") -> Role:
|
|
86
|
+
"""Balanced default."""
|
|
87
|
+
return Role(
|
|
88
|
+
kind=RoleKind.GENERALIST,
|
|
89
|
+
policies=(
|
|
90
|
+
PublishWhenConfident(threshold=0.8, priority=50),
|
|
91
|
+
ConsultOnDisagreement(divergence=0.3, priority=40),
|
|
92
|
+
DisputeOnContradiction(min_gap=0.4, min_rep_delta=0.15, priority=60),
|
|
93
|
+
CheckpointOnSignalGain(kl_threshold=0.1, priority=30),
|
|
94
|
+
),
|
|
95
|
+
caveats=(Caveat.topic(topic),),
|
|
96
|
+
description="balanced default; the v0.10 mind layer's behaviour",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def synthesist(*, topic: str = "*") -> Role:
|
|
101
|
+
"""Aggregator only — caveat enforces synthesis claim type."""
|
|
102
|
+
return Role(
|
|
103
|
+
kind=RoleKind.SYNTHESIST,
|
|
104
|
+
policies=(
|
|
105
|
+
PublishWhenConfident(threshold=0.85, priority=40),
|
|
106
|
+
ConsultOnDisagreement(divergence=0.2, priority=55),
|
|
107
|
+
),
|
|
108
|
+
caveats=(
|
|
109
|
+
Caveat.topic(topic),
|
|
110
|
+
Caveat.claim_type("synthesis"),
|
|
111
|
+
Caveat.rate("10/min"),
|
|
112
|
+
),
|
|
113
|
+
description="only publishes synthesis claims; ≥3 parents required (enforced by application)",
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def mediator(*, topic: str = "*") -> Role:
|
|
118
|
+
"""Adjudication role; cannot publish primary claims (caveat-enforced)."""
|
|
119
|
+
return Role(
|
|
120
|
+
kind=RoleKind.MEDIATOR,
|
|
121
|
+
policies=(DisputeOnContradiction(min_gap=0.5, min_rep_delta=0.2, priority=80),),
|
|
122
|
+
caveats=(
|
|
123
|
+
Caveat.topic(topic),
|
|
124
|
+
Caveat.claim_type("synthesis"), # mediators may only publish summaries
|
|
125
|
+
),
|
|
126
|
+
description="VRF-selected adjudicator; uses select_mediator() for picks",
|
|
127
|
+
publishes_primary=False,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def oracle(*, topic: str = "*") -> Role:
|
|
132
|
+
"""Ground-truth resolver only — publishes oracle_resolution claims."""
|
|
133
|
+
return Role(
|
|
134
|
+
kind=RoleKind.ORACLE,
|
|
135
|
+
policies=(), # oracle doesn't deliberate; it resolves
|
|
136
|
+
caveats=(
|
|
137
|
+
Caveat.topic(topic),
|
|
138
|
+
Caveat.claim_type("oracle_resolution"),
|
|
139
|
+
),
|
|
140
|
+
description="closes the loop; not a deliberator",
|
|
141
|
+
publishes_primary=False,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
_BUILTIN: dict[str, callable] = {
|
|
146
|
+
"scout": scout,
|
|
147
|
+
"sceptic": sceptic,
|
|
148
|
+
"generalist": generalist,
|
|
149
|
+
"synthesist": synthesist,
|
|
150
|
+
"mediator": mediator,
|
|
151
|
+
"oracle": oracle,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def role_by_name(name: str, *, topic: str = "*") -> Role:
|
|
156
|
+
"""Look up a built-in role by its lowercase name."""
|
|
157
|
+
if name not in _BUILTIN:
|
|
158
|
+
raise ValueError(f"unknown role {name!r}; valid: {sorted(_BUILTIN)}")
|
|
159
|
+
return _BUILTIN[name](topic=topic)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# ---------------------------------------------------------------------------
|
|
163
|
+
# Composition — assemble a swarm spec
|
|
164
|
+
# ---------------------------------------------------------------------------
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@dataclass
|
|
168
|
+
class SwarmComposition:
|
|
169
|
+
"""A swarm spec — counts per role. Used by the harness + examples."""
|
|
170
|
+
|
|
171
|
+
counts: dict[RoleKind, int] = field(default_factory=dict)
|
|
172
|
+
|
|
173
|
+
@classmethod
|
|
174
|
+
def of(cls, **kwargs: int) -> SwarmComposition:
|
|
175
|
+
"""``SwarmComposition.of(scout=3, generalist=2, oracle=1)``."""
|
|
176
|
+
out = cls()
|
|
177
|
+
for k, n in kwargs.items():
|
|
178
|
+
out.counts[RoleKind(k)] = int(n)
|
|
179
|
+
return out
|
|
180
|
+
|
|
181
|
+
def total(self) -> int:
|
|
182
|
+
return sum(self.counts.values())
|
|
183
|
+
|
|
184
|
+
def expand(self, *, topic: str = "*") -> list[Role]:
|
|
185
|
+
"""Return the flat list of roles per the configured counts."""
|
|
186
|
+
out: list[Role] = []
|
|
187
|
+
for kind, n in self.counts.items():
|
|
188
|
+
for _ in range(int(n)):
|
|
189
|
+
out.append(role_by_name(kind.value, topic=topic))
|
|
190
|
+
return out
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""SybilGuard — cap influence of new agents in deliberator decisions.
|
|
2
|
+
|
|
3
|
+
Failure mode (design/failure-modes.md:59-78): a vouched newcomer can
|
|
4
|
+
inject up to ~30 claims before the calibration loop detects bad
|
|
5
|
+
predictions. With N pre-built sybils, that scales to N × 30
|
|
6
|
+
undetected injections. The vouch cap (5% of voucher α) is the
|
|
7
|
+
network-side defence; this is the agent-side defence — the
|
|
8
|
+
*deliberator* of an honest agent should refuse to give a newcomer
|
|
9
|
+
full weight in routing/synthesis until they've earned a track record
|
|
10
|
+
of resolved outcomes.
|
|
11
|
+
|
|
12
|
+
API mirrors the existing 5%-of-α vouch cap pattern in
|
|
13
|
+
src/hypermind/knowledge/reputation.py:136-157.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class SybilGuard:
|
|
23
|
+
"""Cap an agent's effective weight by their observation count.
|
|
24
|
+
|
|
25
|
+
``min_observations`` is the threshold for full weight. Below that,
|
|
26
|
+
weight is linearly attenuated down to ``floor_weight``.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
min_observations: int = 30
|
|
30
|
+
floor_weight: float = 0.05
|
|
31
|
+
|
|
32
|
+
def effective_weight(self, raw_weight: float, n_observations: int) -> float:
|
|
33
|
+
if n_observations >= self.min_observations:
|
|
34
|
+
return raw_weight
|
|
35
|
+
ratio = max(0, n_observations) / max(1, self.min_observations)
|
|
36
|
+
capped = self.floor_weight + (1.0 - self.floor_weight) * ratio
|
|
37
|
+
return raw_weight * capped
|
|
38
|
+
|
|
39
|
+
def is_advisory_only(self, n_observations: int) -> bool:
|
|
40
|
+
"""True when the agent's weight is below 50% of full."""
|
|
41
|
+
return n_observations < (self.min_observations // 2)
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""WorldModel + LookaheadPolicy — model-based deliberation.
|
|
2
|
+
|
|
3
|
+
Agents that *plan* against an internal predictive model dramatically
|
|
4
|
+
outperform reactive ones (cf. MuZero/Dreamer). We expose a Protocol
|
|
5
|
+
so production deployments can swap in any predictor (LLM-backed,
|
|
6
|
+
Bayesian, learned model). The sandbox impl is a Beta predictor over
|
|
7
|
+
the agent's own (action, outcome) history.
|
|
8
|
+
|
|
9
|
+
Spec: docs/spec/20-swarm-intelligence.md §20.5.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import time
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
from typing import Protocol, runtime_checkable
|
|
17
|
+
|
|
18
|
+
from hypermind.mind.policy import (
|
|
19
|
+
ActionDecision,
|
|
20
|
+
ActionKind,
|
|
21
|
+
ActionPolicy,
|
|
22
|
+
PolicyContext,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class ImaginedOutcome:
|
|
28
|
+
"""A predicted future state after a hypothetical action."""
|
|
29
|
+
|
|
30
|
+
posterior: float # P(positive outcome) under this action
|
|
31
|
+
confidence: float # how confident the world model is in the prediction
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@runtime_checkable
|
|
35
|
+
class WorldModel(Protocol):
|
|
36
|
+
"""Pluggable predictive model. Production may wire an LLM."""
|
|
37
|
+
|
|
38
|
+
def predict(self, action: ActionKind, ctx: PolicyContext) -> ImaginedOutcome: ...
|
|
39
|
+
|
|
40
|
+
def update_with_outcome(
|
|
41
|
+
self, action: ActionKind, ctx: PolicyContext, observed: float
|
|
42
|
+
) -> None: ...
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
# Default sandbox impl — Beta predictor
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class BetaWorldModel:
|
|
52
|
+
"""Per-action Beta posterior over P(good outcome).
|
|
53
|
+
|
|
54
|
+
Each action kind has its own (alpha, beta). The model is a thin
|
|
55
|
+
self-supervised learner: when an outcome is observed, the matching
|
|
56
|
+
bucket updates. Confidence is `1 - 1 / (alpha + beta)` — saturates
|
|
57
|
+
toward 1.0 as data accumulates (more data ⇒ sharper posterior ⇒
|
|
58
|
+
"more confident"). With the Beta(1,1) prior, confidence starts at
|
|
59
|
+
0.5, matching the prior uncertainty.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
_state: dict[ActionKind, tuple[float, float]] = field(default_factory=dict)
|
|
63
|
+
|
|
64
|
+
def _get(self, action: ActionKind) -> tuple[float, float]:
|
|
65
|
+
return self._state.get(action, (1.0, 1.0))
|
|
66
|
+
|
|
67
|
+
def predict(self, action: ActionKind, ctx: PolicyContext) -> ImaginedOutcome:
|
|
68
|
+
a, b = self._get(action)
|
|
69
|
+
mean = a / (a + b)
|
|
70
|
+
# Boost predictions when the agent's posterior on the topic
|
|
71
|
+
# already supports this action (e.g. high posterior + publish ⇒ high VOI).
|
|
72
|
+
topic_post = (ctx.working_memory.get("posteriors") or {}).get(ctx.topic)
|
|
73
|
+
if topic_post is not None and action is ActionKind.PUBLISH:
|
|
74
|
+
mean = 0.5 * mean + 0.5 * topic_post
|
|
75
|
+
confidence = 1.0 - 1.0 / (a + b)
|
|
76
|
+
return ImaginedOutcome(posterior=mean, confidence=confidence)
|
|
77
|
+
|
|
78
|
+
def update_with_outcome(self, action: ActionKind, ctx: PolicyContext, observed: float) -> None:
|
|
79
|
+
a, b = self._get(action)
|
|
80
|
+
# observed ∈ [0, 1]; treat as fractional outcome.
|
|
81
|
+
a += float(observed)
|
|
82
|
+
b += 1.0 - float(observed)
|
|
83
|
+
self._state[action] = (a, b)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# ---------------------------------------------------------------------------
|
|
87
|
+
# LookaheadPolicy — N-step planning with a per-tick budget
|
|
88
|
+
# ---------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass
|
|
92
|
+
class LookaheadPolicy(ActionPolicy):
|
|
93
|
+
"""Plan ``depth`` steps with the world_model; pick the highest-EV action.
|
|
94
|
+
|
|
95
|
+
For each candidate action (publish / consult / dispute / checkpoint),
|
|
96
|
+
estimate the expected value-of-information (EVI) under the model.
|
|
97
|
+
Highest EVI wins; ties broken by priority.
|
|
98
|
+
|
|
99
|
+
Per-tick budget is a soft time cap; the policy returns the best
|
|
100
|
+
decision found so far if the budget is exhausted.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
world_model: WorldModel = field(default_factory=BetaWorldModel)
|
|
104
|
+
depth: int = 2
|
|
105
|
+
budget_ms: float = 10.0
|
|
106
|
+
priority: int = 65
|
|
107
|
+
candidates: tuple[ActionKind, ...] = (
|
|
108
|
+
ActionKind.PUBLISH,
|
|
109
|
+
ActionKind.CONSULT,
|
|
110
|
+
ActionKind.CHECKPOINT,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
def decide(self, ctx: PolicyContext) -> ActionDecision | None:
|
|
114
|
+
deadline = time.perf_counter() + (self.budget_ms / 1000.0)
|
|
115
|
+
best: tuple[float, ActionKind] | None = None
|
|
116
|
+
for action in self.candidates:
|
|
117
|
+
if time.perf_counter() >= deadline:
|
|
118
|
+
break
|
|
119
|
+
evi = self._evi(action, ctx, depth=self.depth, deadline=deadline)
|
|
120
|
+
if best is None or evi > best[0]:
|
|
121
|
+
best = (evi, action)
|
|
122
|
+
if best is None or best[0] <= 0.0:
|
|
123
|
+
return None
|
|
124
|
+
evi, action = best
|
|
125
|
+
return ActionDecision(
|
|
126
|
+
kind=action,
|
|
127
|
+
rule_id="MIND-LOOKAHEAD",
|
|
128
|
+
priority=self.priority,
|
|
129
|
+
payload={
|
|
130
|
+
"evi": evi,
|
|
131
|
+
"depth": self.depth,
|
|
132
|
+
"posterior": (ctx.working_memory.get("posteriors") or {}).get(ctx.topic, 0.5),
|
|
133
|
+
},
|
|
134
|
+
reason=f"lookahead depth={self.depth}, EVI={evi:.3f}",
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def _evi(self, action: ActionKind, ctx: PolicyContext, *, depth: int, deadline: float) -> float:
|
|
138
|
+
"""Recursive expected value-of-information."""
|
|
139
|
+
if time.perf_counter() >= deadline:
|
|
140
|
+
return 0.0
|
|
141
|
+
outcome = self.world_model.predict(action, ctx)
|
|
142
|
+
if depth <= 0:
|
|
143
|
+
return outcome.posterior * outcome.confidence
|
|
144
|
+
# Discount future-step EVI by 0.7 — bog-standard horizon discount.
|
|
145
|
+
gamma = 0.7
|
|
146
|
+
next_steps = max(
|
|
147
|
+
(
|
|
148
|
+
self._evi(a, ctx, depth=depth - 1, deadline=deadline)
|
|
149
|
+
for a in self.candidates
|
|
150
|
+
if a is not action # don't loop
|
|
151
|
+
),
|
|
152
|
+
default=0.0,
|
|
153
|
+
)
|
|
154
|
+
return outcome.posterior * outcome.confidence + gamma * next_steps
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def update_world_model_from_resolution(
|
|
158
|
+
world_model: WorldModel,
|
|
159
|
+
*,
|
|
160
|
+
action: ActionKind,
|
|
161
|
+
ctx: PolicyContext,
|
|
162
|
+
oracle_outcome: float,
|
|
163
|
+
) -> None:
|
|
164
|
+
"""Wire helper — called when an oracle resolves a claim that came
|
|
165
|
+
from a deliberator-dispatched action."""
|
|
166
|
+
world_model.update_with_outcome(action, ctx, oracle_outcome)
|