cortexm 0.3.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.
- context_m.py +17 -0
- cortexm/__init__.py +45 -0
- cortexm/accel.py +403 -0
- cortexm/api/__init__.py +0 -0
- cortexm/api/chaos.py +118 -0
- cortexm/api/memory.py +635 -0
- cortexm/bench/__init__.py +0 -0
- cortexm/bench/abilities.py +311 -0
- cortexm/bench/baselines.py +89 -0
- cortexm/bench/beam_loader.py +317 -0
- cortexm/bench/generator.py +376 -0
- cortexm/bench/harness.py +211 -0
- cortexm/bench/messy.py +218 -0
- cortexm/bench/micro.py +251 -0
- cortexm/bench/ood.py +443 -0
- cortexm/bench/run.py +137 -0
- cortexm/bridge/__init__.py +0 -0
- cortexm/bridge/dates.py +178 -0
- cortexm/bridge/decoders.py +204 -0
- cortexm/bridge/enrich.py +255 -0
- cortexm/bridge/extractor.py +316 -0
- cortexm/bridge/fallback.py +332 -0
- cortexm/bridge/onnx_runtime.py +158 -0
- cortexm/bridge/patterns.py +760 -0
- cortexm/bridge/ppr.py +104 -0
- cortexm/bridge/prefilter.py +188 -0
- cortexm/bridge/query_extract.py +420 -0
- cortexm/bridge/reader.py +1174 -0
- cortexm/bridge/rerank.py +204 -0
- cortexm/bridge/writer.py +492 -0
- cortexm/cli.py +295 -0
- cortexm/cognition/__init__.py +53 -0
- cortexm/cognition/abstraction.py +192 -0
- cortexm/cognition/analogy.py +159 -0
- cortexm/cognition/engine.py +204 -0
- cortexm/cognition/gaps.py +365 -0
- cortexm/cognition/scanner.py +204 -0
- cortexm/config.py +375 -0
- cortexm/cortexm.py +8 -0
- cortexm/enterprise/__init__.py +0 -0
- cortexm/enterprise/audit.py +178 -0
- cortexm/enterprise/governance.py +239 -0
- cortexm/errors.py +35 -0
- cortexm/features/__init__.py +0 -0
- cortexm/features/git.py +204 -0
- cortexm/features/prefetch.py +88 -0
- cortexm/features/zk.py +105 -0
- cortexm/federation/__init__.py +39 -0
- cortexm/federation/crdt.py +275 -0
- cortexm/federation/fabric.py +109 -0
- cortexm/federation/hlc.py +80 -0
- cortexm/federation/node.py +145 -0
- cortexm/federation/schema_report.py +73 -0
- cortexm/federation/transport.py +164 -0
- cortexm/index/__init__.py +19 -0
- cortexm/index/nsg.py +386 -0
- cortexm/mcp/__init__.py +0 -0
- cortexm/mcp/server.py +985 -0
- cortexm/metrics.py +62 -0
- cortexm/migrate/__init__.py +0 -0
- cortexm/migrate/importers.py +192 -0
- cortexm/provenance/__init__.py +78 -0
- cortexm/provenance/agent.py +214 -0
- cortexm/provenance/cose.py +201 -0
- cortexm/provenance/scitt.py +258 -0
- cortexm/provenance/vc.py +250 -0
- cortexm/security/__init__.py +0 -0
- cortexm/security/crypto.py +162 -0
- cortexm/security/hashes.py +140 -0
- cortexm/security/injection.py +149 -0
- cortexm/security/mind.py +154 -0
- cortexm/security/pii.py +265 -0
- cortexm/security/rbac.py +169 -0
- cortexm/security/sandbox.py +131 -0
- cortexm/security/zk_hamming.py +142 -0
- cortexm/security/zk_sql.py +485 -0
- cortexm/server/__init__.py +0 -0
- cortexm/server/metrics.py +88 -0
- cortexm/server/rest.py +936 -0
- cortexm/server/sparql.py +984 -0
- cortexm/text/__init__.py +0 -0
- cortexm/text/dissim.py +252 -0
- cortexm/text/embedder.py +155 -0
- cortexm/text/fuzzy.py +218 -0
- cortexm/text/idiolect.py +253 -0
- cortexm/text/labse.py +374 -0
- cortexm/text/tokenizer.py +79 -0
- cortexm/trace/__init__.py +0 -0
- cortexm/trace/blob_arena.py +277 -0
- cortexm/trace/consolidate.py +337 -0
- cortexm/trace/contradictions.py +69 -0
- cortexm/trace/dedup.py +114 -0
- cortexm/trace/edges.py +214 -0
- cortexm/trace/fact.py +121 -0
- cortexm/trace/fade.py +245 -0
- cortexm/trace/lifecycle.py +112 -0
- cortexm/trace/rebuild.py +173 -0
- cortexm/trace/rules.py +171 -0
- cortexm/trace/store.py +680 -0
- cortexm/trace/structural.py +183 -0
- cortexm/trace/tmt.py +335 -0
- cortexm/util.py +148 -0
- cortexm/vsa/__init__.py +0 -0
- cortexm/vsa/attribution.py +149 -0
- cortexm/vsa/cleanup.py +161 -0
- cortexm/vsa/codecs.py +397 -0
- cortexm/vsa/hologram_overlay.py +139 -0
- cortexm/vsa/index.py +163 -0
- cortexm/vsa/ops.py +149 -0
- cortexm/vsa/palace.py +446 -0
- cortexm/vsa/role_vectors.py +236 -0
- cortexm/vsa/slb.py +78 -0
- cortexm/vsa/tlsh_trie.py +137 -0
- cortexm/vsa/working_memory.py +249 -0
- cortexm-0.3.0.dist-info/METADATA +482 -0
- cortexm-0.3.0.dist-info/RECORD +120 -0
- cortexm-0.3.0.dist-info/WHEEL +5 -0
- cortexm-0.3.0.dist-info/entry_points.txt +2 -0
- cortexm-0.3.0.dist-info/licenses/LICENSE +190 -0
- cortexm-0.3.0.dist-info/top_level.txt +2 -0
cortexm/mcp/server.py
ADDED
|
@@ -0,0 +1,985 @@
|
|
|
1
|
+
"""Model Context Protocol server — stdio JSON-RPC 2.0.
|
|
2
|
+
|
|
3
|
+
Day-1 MCP support per the plan: any agent (Claude, Cursor, OpenCode)
|
|
4
|
+
discovers Context-M as a native memory tool. Zero dependencies — the
|
|
5
|
+
protocol is implemented directly on line-delimited JSON-RPC.
|
|
6
|
+
|
|
7
|
+
Run: cortexm serve (or: python -m cortexm.mcp.server)
|
|
8
|
+
Config: CONTEXT_M_DB sets the database path; CONTEXT_M_CODEC selects
|
|
9
|
+
the storage tier (int8 | binary | rabitq | pq).
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
from cortexm.config import Config
|
|
19
|
+
from cortexm.api.memory import Memory
|
|
20
|
+
|
|
21
|
+
PROTOCOL_VERSION = "2025-06-18"
|
|
22
|
+
SERVER_INFO = {"name": "context-m", "version": "0.1.0"}
|
|
23
|
+
|
|
24
|
+
TOOLS = [
|
|
25
|
+
{
|
|
26
|
+
"name": "contextm_add",
|
|
27
|
+
"description": "Store memories from a conversation (μ=0: deterministic, "
|
|
28
|
+
"no LLM calls). Accepts a message string or a list of "
|
|
29
|
+
"{role, content} dicts.",
|
|
30
|
+
"inputSchema": {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"properties": {
|
|
33
|
+
"messages": {"type": ["string", "array"]},
|
|
34
|
+
"user_id": {"type": "string", "default": "default"},
|
|
35
|
+
"agent_id": {"type": "string"},
|
|
36
|
+
"run_id": {"type": "string"},
|
|
37
|
+
"timestamp": {"type": "string"},
|
|
38
|
+
},
|
|
39
|
+
"required": ["messages"],
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"name": "contextm_search",
|
|
44
|
+
"description": "Neuro-symbolic memory retrieval with cryptographic "
|
|
45
|
+
"provenance (query → VSA match → symbolic dereference → "
|
|
46
|
+
"source hash → original text).",
|
|
47
|
+
"inputSchema": {
|
|
48
|
+
"type": "object",
|
|
49
|
+
"properties": {
|
|
50
|
+
"query": {"type": "string"},
|
|
51
|
+
"user_id": {"type": "string", "default": "default"},
|
|
52
|
+
"limit": {"type": "integer", "default": 12},
|
|
53
|
+
},
|
|
54
|
+
"required": ["query"],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "contextm_get_all",
|
|
59
|
+
"description": "List all active memories for a user.",
|
|
60
|
+
"inputSchema": {
|
|
61
|
+
"type": "object",
|
|
62
|
+
"properties": {"user_id": {"type": "string"},
|
|
63
|
+
"limit": {"type": "integer", "default": 200}},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"name": "contextm_history",
|
|
68
|
+
"description": "Bi-temporal history of a fact chain, including all "
|
|
69
|
+
"supersessions.",
|
|
70
|
+
"inputSchema": {
|
|
71
|
+
"type": "object",
|
|
72
|
+
"properties": {"memory_id": {"type": "string"}},
|
|
73
|
+
"required": ["memory_id"],
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"name": "contextm_temporal",
|
|
78
|
+
"description": "Zep-compatible temporal query: facts in a time window. "
|
|
79
|
+
"op: before | after | between; field: valid (reality) "
|
|
80
|
+
"or tx (when recorded).",
|
|
81
|
+
"inputSchema": {
|
|
82
|
+
"type": "object",
|
|
83
|
+
"properties": {
|
|
84
|
+
"op": {"type": "string", "enum": ["before", "after", "between"]},
|
|
85
|
+
"start": {"type": "string"},
|
|
86
|
+
"end": {"type": "string"},
|
|
87
|
+
"user_id": {"type": "string"},
|
|
88
|
+
"field": {"type": "string", "default": "valid"},
|
|
89
|
+
},
|
|
90
|
+
"required": ["op"],
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"name": "contextm_audit",
|
|
95
|
+
"description": "The 'Why' audit trail for a query: full provenance "
|
|
96
|
+
"chain with hash verification for every returned fact.",
|
|
97
|
+
"inputSchema": {
|
|
98
|
+
"type": "object",
|
|
99
|
+
"properties": {"query": {"type": "string"},
|
|
100
|
+
"user_id": {"type": "string"}},
|
|
101
|
+
"required": ["query"],
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"name": "contextm_prove",
|
|
106
|
+
"description": "Zero-knowledge-lite proof: prove a matching fact "
|
|
107
|
+
"exists (Merkle membership + attestation) without "
|
|
108
|
+
"revealing its content to the LLM.",
|
|
109
|
+
"inputSchema": {
|
|
110
|
+
"type": "object",
|
|
111
|
+
"properties": {"query": {"type": "string"},
|
|
112
|
+
"user_id": {"type": "string"}},
|
|
113
|
+
"required": ["query"],
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"name": "contextm_stats",
|
|
118
|
+
"description": "Memory fabric statistics: facts, vectors, codec, "
|
|
119
|
+
"SLB hit rate, μ=0 protocol status.",
|
|
120
|
+
"inputSchema": {"type": "object", "properties": {}},
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"name": "contextm_delete",
|
|
124
|
+
"description": "Deactivate a memory (audit-logged, never hard-deleted).",
|
|
125
|
+
"inputSchema": {
|
|
126
|
+
"type": "object",
|
|
127
|
+
"properties": {"memory_id": {"type": "string"}},
|
|
128
|
+
"required": ["memory_id"],
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"name": "contextm_query_extract",
|
|
133
|
+
"description": "Query-time extraction (arXiv 2026 hybrid RAG): retrieve "
|
|
134
|
+
"raw chunks relevant to the query and run the deterministic "
|
|
135
|
+
"extractor on them lazily. Adds extracted_at temporal axis. "
|
|
136
|
+
"Closes the 'half-empty palace' gap for slang/paraphrase.",
|
|
137
|
+
"inputSchema": {
|
|
138
|
+
"type": "object",
|
|
139
|
+
"properties": {
|
|
140
|
+
"query": {"type": "string"},
|
|
141
|
+
"user_id": {"type": "string", "default": "default"},
|
|
142
|
+
"k": {"type": "integer", "default": 5},
|
|
143
|
+
},
|
|
144
|
+
"required": ["query"],
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"name": "contextm_attribution",
|
|
149
|
+
"description": "Source attribution (ProtoDash): for a given query, "
|
|
150
|
+
"show which source chunks contributed to the retrieval "
|
|
151
|
+
"result and with what weights. Audit trail for debugging.",
|
|
152
|
+
"inputSchema": {
|
|
153
|
+
"type": "object",
|
|
154
|
+
"properties": {"query": {"type": "string"},
|
|
155
|
+
"user_id": {"type": "string"}},
|
|
156
|
+
"required": ["query"],
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"name": "contextm_zk_prove",
|
|
161
|
+
"description": "Hamming-distance ZK-style proof on binary vectors: prove "
|
|
162
|
+
"you hold a memory within threshold Hamming distance of a "
|
|
163
|
+
"public commitment, without revealing the memory.",
|
|
164
|
+
"inputSchema": {
|
|
165
|
+
"type": "object",
|
|
166
|
+
"properties": {
|
|
167
|
+
"memory_id": {"type": "string"},
|
|
168
|
+
"public_commitment": {"type": "string"},
|
|
169
|
+
"threshold": {"type": "integer", "default": 32},
|
|
170
|
+
},
|
|
171
|
+
"required": ["memory_id", "public_commitment"],
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"name": "contextm_reconstruct",
|
|
176
|
+
"description": "Active memory reconstruction (MRAgent ICML 2026 lineage). "
|
|
177
|
+
"For a given query, expand seed facts via 2-hop Personalized "
|
|
178
|
+
"PageRank, score each hop's relevance, prune low-scoring "
|
|
179
|
+
"branches, and return a synthesized narrative view of the "
|
|
180
|
+
"user's memory state. Use this when a normal search would "
|
|
181
|
+
"miss multi-hop context (e.g. 'what's the connection between "
|
|
182
|
+
"Alice's job and her sister's hobby?').",
|
|
183
|
+
"inputSchema": {
|
|
184
|
+
"type": "object",
|
|
185
|
+
"properties": {
|
|
186
|
+
"query": {"type": "string"},
|
|
187
|
+
"user_id": {"type": "string", "default": "default"},
|
|
188
|
+
"k": {"type": "integer", "default": 10},
|
|
189
|
+
"max_hops": {"type": "integer", "default": 3},
|
|
190
|
+
},
|
|
191
|
+
"required": ["query"],
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"name": "contextm_consolidate",
|
|
196
|
+
"description": "Trigger a memory consolidation pass on demand (normally "
|
|
197
|
+
"runs nightly via cron). Runs the FadeMem sweep "
|
|
198
|
+
"(decay + deactivate + merge), TiMem TMT hierarchy build "
|
|
199
|
+
"(session→day→persona summaries), and Aeon-style dreaming "
|
|
200
|
+
"(merge redundant triples, defrag palace). Measured 43.2%% "
|
|
201
|
+
"storage reduction with zero retrieval-precision regression. "
|
|
202
|
+
"Returns a stats dict; safe to call repeatedly (idempotent).",
|
|
203
|
+
"inputSchema": {
|
|
204
|
+
"type": "object",
|
|
205
|
+
"properties": {
|
|
206
|
+
"user_id": {"type": "string", "default": "default"},
|
|
207
|
+
"dry_run": {"type": "boolean", "default": False},
|
|
208
|
+
"lifecycle": {"type": "boolean", "default": True},
|
|
209
|
+
"dreaming": {"type": "boolean", "default": True},
|
|
210
|
+
"fade": {"type": "boolean", "default": True},
|
|
211
|
+
"tmt": {"type": "boolean", "default": True},
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"name": "contextm_working_memory",
|
|
217
|
+
"description": "Holographic working memory — compress the top-k retrieved "
|
|
218
|
+
"facts into a single HRR (Holographic Reduced Representation) "
|
|
219
|
+
"superposition. Returns a short (~30-50 token) preamble for "
|
|
220
|
+
"LLM system-prompt injection + the HRR vector base64-packed. "
|
|
221
|
+
"5-10× token reduction for context windows with repeated "
|
|
222
|
+
"memory injection across turns.",
|
|
223
|
+
"inputSchema": {
|
|
224
|
+
"type": "object",
|
|
225
|
+
"properties": {
|
|
226
|
+
"query": {"type": "string"},
|
|
227
|
+
"user_id": {"type": "string", "default": "default"},
|
|
228
|
+
"k": {"type": "integer", "default": 12},
|
|
229
|
+
},
|
|
230
|
+
"required": ["query"],
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
"name": "contextm_hologram_extract",
|
|
235
|
+
"description": "Unbind a role (S/R/V) from a holographic working memory "
|
|
236
|
+
"vector and return the top-3 candidate facts. Used by agents "
|
|
237
|
+
"that received a working-memory hologram via "
|
|
238
|
+
"contextm_working_memory and want to recall a specific fact "
|
|
239
|
+
"slot on demand. Pure HRR algebra — μ=0.",
|
|
240
|
+
"inputSchema": {
|
|
241
|
+
"type": "object",
|
|
242
|
+
"properties": {
|
|
243
|
+
"hrr_b64": {"type": "string",
|
|
244
|
+
"description": "base64-packed HRR vector from contextm_working_memory"},
|
|
245
|
+
"role": {"type": "string", "enum": ["S", "R", "V"]},
|
|
246
|
+
"candidate_ids": {"type": "array", "items": {"type": "string"}},
|
|
247
|
+
"user_id": {"type": "string", "default": "default"},
|
|
248
|
+
},
|
|
249
|
+
"required": ["hrr_b64", "role", "candidate_ids"],
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
"name": "contextm_zk_sql_proof",
|
|
254
|
+
"description": "ZK-SQL proof (Halo2/PLONKish-inspired, pure-Python): "
|
|
255
|
+
"prove a SQL-style aggregate over the Trace without "
|
|
256
|
+
"revealing the underlying facts. PoneglyphDB-style — "
|
|
257
|
+
"verifier sees only the claimed result + Merkle root + "
|
|
258
|
+
"a non-interactive PLONKish transcript, never the actual "
|
|
259
|
+
"matching facts. Requires Config.zk_sql_enabled=True "
|
|
260
|
+
"(default OFF — proof generation is O(N) in trace size). "
|
|
261
|
+
"query: 'membership' | 'count' | 'sum' | 'avg' | 'min' | "
|
|
262
|
+
"'max'. For 'membership', supply subject+relation (and "
|
|
263
|
+
"optionally value). For aggregates, supply relation; "
|
|
264
|
+
"value_filter narrows by substring; user_id scopes the "
|
|
265
|
+
"trace. Returns {proof_id, query, claimed_result, "
|
|
266
|
+
"merkle_root, verify, transcript_size}.",
|
|
267
|
+
"inputSchema": {
|
|
268
|
+
"type": "object",
|
|
269
|
+
"properties": {
|
|
270
|
+
"query": {"type": "string",
|
|
271
|
+
"enum": ["membership", "count", "sum", "avg",
|
|
272
|
+
"min", "max"]},
|
|
273
|
+
"subject": {"type": "string"},
|
|
274
|
+
"relation": {"type": "string"},
|
|
275
|
+
"value": {"type": "string"},
|
|
276
|
+
"value_filter": {"type": "string",
|
|
277
|
+
"description": "substring filter on the value column"},
|
|
278
|
+
"user_id": {"type": "string"},
|
|
279
|
+
},
|
|
280
|
+
"required": ["query", "relation"],
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
"name": "contextm_provenance_export",
|
|
285
|
+
"description": "W3C Verifiable Credential + COSE Sign1 + SCITT receipt "
|
|
286
|
+
"export for a memory range. Enterprise-grade provenance: "
|
|
287
|
+
"emits a W3C VC 2.0 with eddsa-jcs-2022 Data Integrity "
|
|
288
|
+
"proof (BLAKE3 Merkle root + agent did:key signature), "
|
|
289
|
+
"wraps the issuing commit in a COSE Sign1 envelope, and "
|
|
290
|
+
"submits the envelope to a SCITT transparency log. "
|
|
291
|
+
"External verifiers can confirm: (1) the memory range "
|
|
292
|
+
"was signed by a known agent (COSE Sign1), (2) the "
|
|
293
|
+
"Merkle root is correct (VC), (3) it was logged in a "
|
|
294
|
+
"transparency log (SCITT receipt) — without seeing the "
|
|
295
|
+
"individual facts. Requires Config.provenance_enabled="
|
|
296
|
+
"True (default OFF).",
|
|
297
|
+
"inputSchema": {
|
|
298
|
+
"type": "object",
|
|
299
|
+
"properties": {
|
|
300
|
+
"user_id": {"type": "string"},
|
|
301
|
+
"valid_from": {"type": "string",
|
|
302
|
+
"description": "ISO timestamp; facts with valid_from >= this"},
|
|
303
|
+
"valid_to": {"type": "string",
|
|
304
|
+
"description": "ISO timestamp; facts with valid_to <= this"},
|
|
305
|
+
"include_hypotheses": {"type": "boolean",
|
|
306
|
+
"description": "include cognition-engine derived facts"},
|
|
307
|
+
"submit_to_scitt": {"type": "boolean", "default": True},
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
"name": "contextm_structural_query",
|
|
313
|
+
"description": "Deterministic multi-hop relation chaining. Walks "
|
|
314
|
+
"exact symbolic relation chains via Trace lookups + "
|
|
315
|
+
"VSA unbinding fallback. Example: 'father', 'father' "
|
|
316
|
+
"answers 'Who is X's grandfather?' by following "
|
|
317
|
+
"subject=X → relation=father → value=Y → relation="
|
|
318
|
+
"father → value=Z. Complementary to PPR (probabilistic "
|
|
319
|
+
"graph diffusion) — PPR answers 'what else might be "
|
|
320
|
+
"relevant?', structural_query answers 'exactly follow "
|
|
321
|
+
"this chain.' Returns {final_value, hops, confidence, "
|
|
322
|
+
"success}.",
|
|
323
|
+
"inputSchema": {
|
|
324
|
+
"type": "object",
|
|
325
|
+
"properties": {
|
|
326
|
+
"start_entity": {"type": "string"},
|
|
327
|
+
"relation_chain": {
|
|
328
|
+
"type": "array",
|
|
329
|
+
"items": {"type": "string"},
|
|
330
|
+
"description": "ordered list of relations to follow",
|
|
331
|
+
},
|
|
332
|
+
"user_id": {"type": "string"},
|
|
333
|
+
"allow_hypotheses": {
|
|
334
|
+
"type": "boolean",
|
|
335
|
+
"default": False,
|
|
336
|
+
"description": "if True, also follow HYPOTHESIZED_BY edges"},
|
|
337
|
+
"vsa_fallback": {
|
|
338
|
+
"type": "boolean", "default": True,
|
|
339
|
+
"description": "if no symbolic match, use VSA unbind + cleanup"},
|
|
340
|
+
},
|
|
341
|
+
"required": ["start_entity", "relation_chain"],
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
"name": "contextm_cognition_run",
|
|
346
|
+
"description": "Run the HMS-style Cognition Engine pass on demand. "
|
|
347
|
+
"Five stages: PatternScanner (surface structural "
|
|
348
|
+
"regularities), AbstractionEngine (build prototype "
|
|
349
|
+
"categories), GapDetector (find missing relations), "
|
|
350
|
+
"HypothesisEngine (propose fillers), AnalogyDetector "
|
|
351
|
+
"(find structurally isomorphic domains). Output is "
|
|
352
|
+
"HYPOTHESIZED_BY edges with confidence < 0.5 — never "
|
|
353
|
+
"promoted to active retrieval unless explicitly "
|
|
354
|
+
"confirmed. Useful for triggering self-organization "
|
|
355
|
+
"outside the nightly consolidate() cron.",
|
|
356
|
+
"inputSchema": {
|
|
357
|
+
"type": "object",
|
|
358
|
+
"properties": {
|
|
359
|
+
"user_id": {"type": "string"},
|
|
360
|
+
"dry_run": {"type": "boolean", "default": False},
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
]
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
class MCPServer:
|
|
368
|
+
def __init__(self, memory: Memory) -> None:
|
|
369
|
+
self.memory = memory
|
|
370
|
+
|
|
371
|
+
# ------------------------------------------------------------------
|
|
372
|
+
def handle(self, request: dict) -> dict | None:
|
|
373
|
+
method = request.get("method", "")
|
|
374
|
+
req_id = request.get("id")
|
|
375
|
+
if method == "initialize":
|
|
376
|
+
return self._ok(req_id, {
|
|
377
|
+
"protocolVersion": PROTOCOL_VERSION,
|
|
378
|
+
"capabilities": {"tools": {"listChanged": False}},
|
|
379
|
+
"serverInfo": SERVER_INFO,
|
|
380
|
+
})
|
|
381
|
+
if method == "notifications/initialized":
|
|
382
|
+
return None
|
|
383
|
+
if method == "ping":
|
|
384
|
+
return self._ok(req_id, {})
|
|
385
|
+
if method == "tools/list":
|
|
386
|
+
return self._ok(req_id, {"tools": TOOLS})
|
|
387
|
+
if method == "tools/call":
|
|
388
|
+
return self._ok(req_id, self._call_tool(
|
|
389
|
+
request.get("params", {}).get("name", ""),
|
|
390
|
+
request.get("params", {}).get("arguments", {}) or {}))
|
|
391
|
+
if method == "resources/list":
|
|
392
|
+
return self._ok(req_id, {"resources": []})
|
|
393
|
+
if req_id is not None:
|
|
394
|
+
return self._err(req_id, -32601, f"method not found: {method}")
|
|
395
|
+
return None
|
|
396
|
+
|
|
397
|
+
# ------------------------------------------------------------------
|
|
398
|
+
def _call_tool(self, name: str, args: dict) -> dict:
|
|
399
|
+
m = self.memory
|
|
400
|
+
try:
|
|
401
|
+
if name == "contextm_add":
|
|
402
|
+
out = m.add(args.get("messages"),
|
|
403
|
+
user_id=args.get("user_id", "default"),
|
|
404
|
+
agent_id=args.get("agent_id"),
|
|
405
|
+
run_id=args.get("run_id"),
|
|
406
|
+
timestamp=args.get("timestamp"))
|
|
407
|
+
text = json.dumps({"stored": len(out.get("results", [])),
|
|
408
|
+
"stats": out.get("stats")}, default=str)
|
|
409
|
+
elif name == "contextm_search":
|
|
410
|
+
out = m.search(args.get("query", ""),
|
|
411
|
+
user_id=args.get("user_id", "default"),
|
|
412
|
+
limit=args.get("limit", 12))
|
|
413
|
+
text = out["context_block"]
|
|
414
|
+
elif name == "contextm_get_all":
|
|
415
|
+
out = m.get_all(user_id=args.get("user_id", "default"),
|
|
416
|
+
limit=args.get("limit", 200))
|
|
417
|
+
text = "\n".join(r["memory"] for r in out["results"]) or "(empty)"
|
|
418
|
+
elif name == "contextm_history":
|
|
419
|
+
out = m.history(args.get("memory_id", ""))
|
|
420
|
+
text = json.dumps(out, indent=1, default=str)
|
|
421
|
+
elif name == "contextm_temporal":
|
|
422
|
+
op = args.get("op", "between")
|
|
423
|
+
if op == "before":
|
|
424
|
+
out = m.get_before(args.get("end") or args.get("start"),
|
|
425
|
+
user_id=args.get("user_id", "default"),
|
|
426
|
+
field=args.get("field", "valid"))
|
|
427
|
+
elif op == "after":
|
|
428
|
+
out = m.get_after(args.get("start"),
|
|
429
|
+
user_id=args.get("user_id", "default"),
|
|
430
|
+
field=args.get("field", "valid"))
|
|
431
|
+
else:
|
|
432
|
+
out = m.get_between(args.get("start"), args.get("end"),
|
|
433
|
+
user_id=args.get("user_id", "default"),
|
|
434
|
+
field=args.get("field", "valid"))
|
|
435
|
+
text = json.dumps(out, indent=1, default=str)
|
|
436
|
+
elif name == "contextm_audit":
|
|
437
|
+
out = m.audit(args.get("query", ""),
|
|
438
|
+
user_id=args.get("user_id", "default"))
|
|
439
|
+
text = json.dumps(out, indent=1, default=str)
|
|
440
|
+
elif name == "contextm_prove":
|
|
441
|
+
proof = m.prove(args.get("query", ""),
|
|
442
|
+
user_id=args.get("user_id", "default"))
|
|
443
|
+
text = proof["llm_view"] + f"\nMerkle root: {proof['merkle_root'][:16]}…"
|
|
444
|
+
elif name == "contextm_stats":
|
|
445
|
+
text = json.dumps(m.stats(), indent=1, default=str)
|
|
446
|
+
elif name == "contextm_delete":
|
|
447
|
+
out = m.delete(args.get("memory_id", ""))
|
|
448
|
+
text = json.dumps(out)
|
|
449
|
+
elif name == "contextm_query_extract":
|
|
450
|
+
# Query-time extraction (hybrid RAG) — extract from raw
|
|
451
|
+
# chunks relevant to the query, even if μ=0 ingest missed.
|
|
452
|
+
out = self._query_extract(
|
|
453
|
+
args.get("query", ""),
|
|
454
|
+
user_id=args.get("user_id", "default"),
|
|
455
|
+
k=args.get("k", 5))
|
|
456
|
+
text = json.dumps(out, indent=1, default=str)
|
|
457
|
+
elif name == "contextm_attribution":
|
|
458
|
+
# ProtoDash source attribution — which chunks contributed.
|
|
459
|
+
out = self._attribution(
|
|
460
|
+
args.get("query", ""),
|
|
461
|
+
user_id=args.get("user_id", "default"))
|
|
462
|
+
text = json.dumps(out, indent=1, default=str)
|
|
463
|
+
elif name == "contextm_zk_prove":
|
|
464
|
+
# Hamming ZK proof on binary codec vectors.
|
|
465
|
+
out = self._zk_prove(
|
|
466
|
+
args.get("memory_id", ""),
|
|
467
|
+
args.get("public_commitment", ""),
|
|
468
|
+
threshold=args.get("threshold", 32))
|
|
469
|
+
text = json.dumps(out, indent=1, default=str)
|
|
470
|
+
elif name == "contextm_reconstruct":
|
|
471
|
+
# MRAgent-style active reconstruction.
|
|
472
|
+
out = self._reconstruct(
|
|
473
|
+
args.get("query", ""),
|
|
474
|
+
user_id=args.get("user_id", "default"),
|
|
475
|
+
k=args.get("k", 10),
|
|
476
|
+
max_hops=args.get("max_hops", 3))
|
|
477
|
+
text = json.dumps(out, indent=1, default=str)
|
|
478
|
+
elif name == "contextm_consolidate":
|
|
479
|
+
# On-demand consolidation pass (FadeMem + TMT + dreaming).
|
|
480
|
+
out = self._consolidate(
|
|
481
|
+
user_id=args.get("user_id", "default"),
|
|
482
|
+
dry_run=args.get("dry_run", False),
|
|
483
|
+
lifecycle=args.get("lifecycle", True),
|
|
484
|
+
dreaming=args.get("dreaming", True),
|
|
485
|
+
fade=args.get("fade", True),
|
|
486
|
+
tmt=args.get("tmt", True))
|
|
487
|
+
text = json.dumps(out, indent=1, default=str)
|
|
488
|
+
elif name == "contextm_working_memory":
|
|
489
|
+
# Holographic working-memory compression.
|
|
490
|
+
out = self._working_memory(
|
|
491
|
+
args.get("query", ""),
|
|
492
|
+
user_id=args.get("user_id", "default"),
|
|
493
|
+
k=args.get("k", 12))
|
|
494
|
+
text = json.dumps(out, indent=1, default=str)
|
|
495
|
+
elif name == "contextm_hologram_extract":
|
|
496
|
+
# Unbind a role from a HRR hologram and return top-3.
|
|
497
|
+
out = self._hologram_extract(
|
|
498
|
+
args.get("hrr_b64", ""),
|
|
499
|
+
args.get("role", "S"),
|
|
500
|
+
args.get("candidate_ids", []),
|
|
501
|
+
user_id=args.get("user_id", "default"))
|
|
502
|
+
text = json.dumps(out, indent=1, default=str)
|
|
503
|
+
elif name == "contextm_zk_sql_proof":
|
|
504
|
+
# ZK-SQL proof (PLONKish-inspired, pure-Python).
|
|
505
|
+
out = self._zk_sql_proof(
|
|
506
|
+
args.get("query", "count"),
|
|
507
|
+
subject=args.get("subject"),
|
|
508
|
+
relation=args.get("relation"),
|
|
509
|
+
value=args.get("value"),
|
|
510
|
+
value_filter=args.get("value_filter"),
|
|
511
|
+
user_id=args.get("user_id"))
|
|
512
|
+
text = json.dumps(out, indent=1, default=str)
|
|
513
|
+
elif name == "contextm_provenance_export":
|
|
514
|
+
out = self._provenance_export(
|
|
515
|
+
user_id=args.get("user_id"),
|
|
516
|
+
valid_from=args.get("valid_from"),
|
|
517
|
+
valid_to=args.get("valid_to"),
|
|
518
|
+
include_hypotheses=args.get("include_hypotheses", False),
|
|
519
|
+
submit_to_scitt=args.get("submit_to_scitt", True))
|
|
520
|
+
text = json.dumps(out, indent=1, default=str)
|
|
521
|
+
elif name == "contextm_structural_query":
|
|
522
|
+
out = self._structural_query(
|
|
523
|
+
args.get("start_entity", ""),
|
|
524
|
+
args.get("relation_chain", []),
|
|
525
|
+
user_id=args.get("user_id"),
|
|
526
|
+
allow_hypotheses=args.get("allow_hypotheses", False),
|
|
527
|
+
vsa_fallback=args.get("vsa_fallback", True))
|
|
528
|
+
text = json.dumps(out, indent=1, default=str)
|
|
529
|
+
elif name == "contextm_cognition_run":
|
|
530
|
+
out = self._cognition_run(
|
|
531
|
+
user_id=args.get("user_id"),
|
|
532
|
+
dry_run=args.get("dry_run", False))
|
|
533
|
+
text = json.dumps(out, indent=1, default=str)
|
|
534
|
+
else:
|
|
535
|
+
return {"content": [{"type": "text",
|
|
536
|
+
"text": f"unknown tool {name}"}],
|
|
537
|
+
"isError": True}
|
|
538
|
+
return {"content": [{"type": "text", "text": text}]}
|
|
539
|
+
except Exception as e: # surface errors to the agent
|
|
540
|
+
return {"content": [{"type": "text", "text": f"error: {e}"}],
|
|
541
|
+
"isError": True}
|
|
542
|
+
|
|
543
|
+
# ------------------------------------------------------------------
|
|
544
|
+
def _query_extract(self, query: str, user_id: str = "default",
|
|
545
|
+
k: int = 5) -> dict:
|
|
546
|
+
"""Hybrid query-time extraction. Uses QueryTimeExtractor if
|
|
547
|
+
available; falls back to standard search results if not."""
|
|
548
|
+
try:
|
|
549
|
+
from cortexm.bridge.query_extract import QueryTimeExtractor
|
|
550
|
+
from cortexm.text.dissim import DisSimSplitter
|
|
551
|
+
from cortexm.text.idiolect import PerUserIdiolectNormalizer
|
|
552
|
+
from cortexm.text.embedder import HashingEmbedder
|
|
553
|
+
|
|
554
|
+
palace = self.memory.palace
|
|
555
|
+
store = self.memory.store
|
|
556
|
+
embedder = HashingEmbedder(palace.dims, palace.cfg.seed)
|
|
557
|
+
dissim = DisSimSplitter(max_depth=2)
|
|
558
|
+
idiolect = PerUserIdiolectNormalizer(embedder)
|
|
559
|
+
extractor = QueryTimeExtractor(
|
|
560
|
+
palace, store, embedder, dissim=dissim,
|
|
561
|
+
idiolect=idiolect,
|
|
562
|
+
pattern_extractor=self.memory.extractor if hasattr(
|
|
563
|
+
self.memory, "extractor") else None)
|
|
564
|
+
results = extractor.query(query, user_id=user_id, k=k)
|
|
565
|
+
return {
|
|
566
|
+
"query": query,
|
|
567
|
+
"user_id": user_id,
|
|
568
|
+
"extracted_count": len(results),
|
|
569
|
+
"results": results,
|
|
570
|
+
"path": "query_time_pattern",
|
|
571
|
+
}
|
|
572
|
+
except Exception as e:
|
|
573
|
+
# graceful fallback: standard search
|
|
574
|
+
out = self.memory.search(query, user_id=user_id, limit=k)
|
|
575
|
+
return {
|
|
576
|
+
"query": query,
|
|
577
|
+
"user_id": user_id,
|
|
578
|
+
"fallback": "standard_search",
|
|
579
|
+
"error": str(e),
|
|
580
|
+
"context_block": out.get("context_block", ""),
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
def _attribution(self, query: str, user_id: str = "default") -> dict:
|
|
584
|
+
"""ProtoDash attribution for a query — which source chunks contributed."""
|
|
585
|
+
try:
|
|
586
|
+
from cortexm.vsa.attribution import ProtoDashAttributer, sentence_level_score
|
|
587
|
+
from cortexm.text.embedder import HashingEmbedder
|
|
588
|
+
import numpy as np
|
|
589
|
+
|
|
590
|
+
# standard search to get candidates
|
|
591
|
+
out = self.memory.search(query, user_id=user_id, limit=10)
|
|
592
|
+
results = out.get("results", [])
|
|
593
|
+
if not results:
|
|
594
|
+
return {"query": query, "attributions": []}
|
|
595
|
+
embedder = HashingEmbedder(self.memory.palace.dims,
|
|
596
|
+
self.memory.palace.cfg.seed)
|
|
597
|
+
q_emb = embedder.embed(query)
|
|
598
|
+
cand_embs = np.stack([embedder.embed(r.get("memory", ""))
|
|
599
|
+
for r in results])
|
|
600
|
+
cand_ids = [r.get("id", str(i)) for i, r in enumerate(results)]
|
|
601
|
+
attrib = ProtoDashAttributer(kernel="linear")
|
|
602
|
+
weights = attrib.attribute(q_emb, cand_embs, cand_ids, m=5)
|
|
603
|
+
return {
|
|
604
|
+
"query": query,
|
|
605
|
+
"user_id": user_id,
|
|
606
|
+
"attributions": [
|
|
607
|
+
{"fact_id": fid, "weight": w, "memory": r.get("memory", "")}
|
|
608
|
+
for (fid, w), r in zip(weights, results)
|
|
609
|
+
],
|
|
610
|
+
}
|
|
611
|
+
except Exception as e:
|
|
612
|
+
return {"query": query, "error": str(e), "attributions": []}
|
|
613
|
+
|
|
614
|
+
def _zk_prove(self, memory_id: str, public_commitment: str,
|
|
615
|
+
threshold: int = 32) -> dict:
|
|
616
|
+
"""Hamming ZK proof on binary codec vectors."""
|
|
617
|
+
try:
|
|
618
|
+
from cortexm.security.zk_hamming import HammingZKProver
|
|
619
|
+
palace = self.memory.palace
|
|
620
|
+
# fetch the memory's packed vector
|
|
621
|
+
row = palace._id2row.get(memory_id)
|
|
622
|
+
if row is None:
|
|
623
|
+
return {"error": f"memory {memory_id} not in palace"}
|
|
624
|
+
packed = palace._packed[row]
|
|
625
|
+
# convert public commitment hex → bytes
|
|
626
|
+
public = bytes.fromhex(public_commitment)
|
|
627
|
+
private = bytes(packed.tobytes())
|
|
628
|
+
prover = HammingZKProver(dims=palace.dims, threshold=threshold)
|
|
629
|
+
proof = prover.prove(public, private)
|
|
630
|
+
verified = prover.verify(public, proof)
|
|
631
|
+
return {
|
|
632
|
+
"memory_id": memory_id,
|
|
633
|
+
"verified": verified,
|
|
634
|
+
"weight": proof.weight,
|
|
635
|
+
"threshold": proof.threshold,
|
|
636
|
+
"commitment": proof.commitment[:32] + "...",
|
|
637
|
+
}
|
|
638
|
+
except Exception as e:
|
|
639
|
+
return {"memory_id": memory_id, "error": str(e)}
|
|
640
|
+
|
|
641
|
+
# ------------------------------------------------------------------
|
|
642
|
+
def _reconstruct(self, query: str, user_id: str = "default",
|
|
643
|
+
k: int = 10, max_hops: int = 3) -> dict:
|
|
644
|
+
"""MRAgent-style active memory reconstruction.
|
|
645
|
+
|
|
646
|
+
Uses Memory.reader.reconstruct() (which exists from prior work
|
|
647
|
+
— bridge/reader.py). Falls back to standard search if the
|
|
648
|
+
reconstruct path is disabled in Config.
|
|
649
|
+
"""
|
|
650
|
+
try:
|
|
651
|
+
reader = getattr(self.memory, "reader", None)
|
|
652
|
+
if reader is None:
|
|
653
|
+
out = self.memory.search(query, user_id=user_id, limit=k)
|
|
654
|
+
return {"query": query, "fallback": "standard_search",
|
|
655
|
+
"context_block": out.get("context_block", "")}
|
|
656
|
+
reconstruct_fn = getattr(reader, "reconstruct", None)
|
|
657
|
+
if (reconstruct_fn is None
|
|
658
|
+
or not getattr(self.memory.config,
|
|
659
|
+
"reconstruct_enabled", False)):
|
|
660
|
+
out = self.memory.search(query, user_id=user_id, limit=k * 2)
|
|
661
|
+
return {"query": query, "fallback": "standard_search_wide",
|
|
662
|
+
"context_block": out.get("context_block", ""),
|
|
663
|
+
"results": out.get("results", [])[:k]}
|
|
664
|
+
res = reconstruct_fn(query, user_id=user_id, k=k,
|
|
665
|
+
max_hops=max_hops)
|
|
666
|
+
return {
|
|
667
|
+
"query": query,
|
|
668
|
+
"user_id": user_id,
|
|
669
|
+
"intent": getattr(res, "intent", "reconstruct"),
|
|
670
|
+
"narrative": getattr(res, "context_block", ""),
|
|
671
|
+
"facts": [
|
|
672
|
+
{"id": getattr(f, "id", ""), "subject": f.subject,
|
|
673
|
+
"relation": f.relation, "value": f.value,
|
|
674
|
+
"confidence": getattr(f, "confidence", 0.0)}
|
|
675
|
+
for f in getattr(res, "facts", [])
|
|
676
|
+
],
|
|
677
|
+
"provenance": getattr(res, "provenance", {}),
|
|
678
|
+
"timing": getattr(res, "timing", {}),
|
|
679
|
+
}
|
|
680
|
+
except Exception as e:
|
|
681
|
+
return {"query": query, "error": str(e)}
|
|
682
|
+
|
|
683
|
+
# ------------------------------------------------------------------
|
|
684
|
+
def _consolidate(self, user_id: str = "default", dry_run: bool = False,
|
|
685
|
+
lifecycle: bool = True, dreaming: bool = True,
|
|
686
|
+
fade: bool = True, tmt: bool = True) -> dict:
|
|
687
|
+
"""On-demand consolidation: FadeMem + TMT + Aeon dreaming.
|
|
688
|
+
|
|
689
|
+
Wraps Memory.consolidate() with explicit fade/tmt toggles so
|
|
690
|
+
callers can run a partial pass (e.g. fade-only) on demand.
|
|
691
|
+
Idempotent — safe to call repeatedly.
|
|
692
|
+
"""
|
|
693
|
+
try:
|
|
694
|
+
out = self.memory.consolidate(
|
|
695
|
+
lifecycle=lifecycle, dreaming=dreaming,
|
|
696
|
+
run_fade=fade, run_tmt=tmt, user_id=user_id,
|
|
697
|
+
dry_run=dry_run)
|
|
698
|
+
return {"user_id": user_id, "dry_run": dry_run,
|
|
699
|
+
"lifecycle": out.get("lifecycle", {}),
|
|
700
|
+
"dreaming": out.get("dreaming", {})}
|
|
701
|
+
except Exception as e:
|
|
702
|
+
return {"user_id": user_id, "error": str(e)}
|
|
703
|
+
|
|
704
|
+
# ------------------------------------------------------------------
|
|
705
|
+
def _working_memory(self, query: str, user_id: str = "default",
|
|
706
|
+
k: int = 12) -> dict:
|
|
707
|
+
"""Compress top-k retrieved facts into a single HRR superposition."""
|
|
708
|
+
try:
|
|
709
|
+
reader = getattr(self.memory, "reader", None)
|
|
710
|
+
if reader is None:
|
|
711
|
+
return {"query": query, "error": "reader not available"}
|
|
712
|
+
return reader.working_memory(query, user_id=user_id, k=k)
|
|
713
|
+
except Exception as e:
|
|
714
|
+
return {"query": query, "error": str(e)}
|
|
715
|
+
|
|
716
|
+
# ------------------------------------------------------------------
|
|
717
|
+
def _hologram_extract(self, hrr_b64: str, role: str,
|
|
718
|
+
candidate_ids: list[str],
|
|
719
|
+
user_id: str = "default") -> dict:
|
|
720
|
+
"""Unbind a role from a HRR hologram and return top-3 matches."""
|
|
721
|
+
try:
|
|
722
|
+
reader = getattr(self.memory, "reader", None)
|
|
723
|
+
if reader is None:
|
|
724
|
+
return {"error": "reader not available"}
|
|
725
|
+
hits = reader.hologram_extract(hrr_b64, role,
|
|
726
|
+
candidate_ids=candidate_ids,
|
|
727
|
+
user_id=user_id)
|
|
728
|
+
return {"role": role, "hits": hits}
|
|
729
|
+
except Exception as e:
|
|
730
|
+
return {"error": str(e)}
|
|
731
|
+
|
|
732
|
+
# ------------------------------------------------------------------
|
|
733
|
+
def _zk_sql_proof(self, query: str, *, subject: str | None = None,
|
|
734
|
+
relation: str | None = None, value: str | None = None,
|
|
735
|
+
value_filter: str | None = None,
|
|
736
|
+
user_id: str | None = None) -> dict:
|
|
737
|
+
"""Generate a ZK-SQL proof over the trace.
|
|
738
|
+
|
|
739
|
+
Honors ``Config.zk_sql_enabled``: if the flag is off (default),
|
|
740
|
+
the tool returns a disabled stub (so prod deployments without
|
|
741
|
+
the O(N) budget aren't accidentally paying for proofs).
|
|
742
|
+
"""
|
|
743
|
+
try:
|
|
744
|
+
if not getattr(self.memory.config, "zk_sql_enabled", False):
|
|
745
|
+
return {
|
|
746
|
+
"query": query,
|
|
747
|
+
"disabled": True,
|
|
748
|
+
"reason": "Config.zk_sql_enabled is False — set it True "
|
|
749
|
+
"to opt into ZK-SQL proofs (O(N) in trace size).",
|
|
750
|
+
}
|
|
751
|
+
from cortexm.security.zk_sql import ZkSqlProver
|
|
752
|
+
prover = ZkSqlProver(self.memory.store,
|
|
753
|
+
self.memory.store.hasher)
|
|
754
|
+
q = (query or "").lower()
|
|
755
|
+
if q == "membership":
|
|
756
|
+
if not subject or not relation:
|
|
757
|
+
return {"error": "membership requires subject + relation"}
|
|
758
|
+
proof = prover.membership_proof(subject, relation, value=value)
|
|
759
|
+
elif q == "count":
|
|
760
|
+
proof = prover.count_proof(relation or "", user_id=user_id)
|
|
761
|
+
elif q == "sum":
|
|
762
|
+
proof = prover.sum_proof(relation or "",
|
|
763
|
+
value_filter=value_filter,
|
|
764
|
+
user_id=user_id)
|
|
765
|
+
elif q == "avg":
|
|
766
|
+
proof = prover.avg_proof(relation or "",
|
|
767
|
+
value_filter=value_filter,
|
|
768
|
+
user_id=user_id)
|
|
769
|
+
elif q == "min":
|
|
770
|
+
proof = prover.minmax_proof(relation or "", "MIN",
|
|
771
|
+
value_filter=value_filter,
|
|
772
|
+
user_id=user_id)
|
|
773
|
+
elif q == "max":
|
|
774
|
+
proof = prover.minmax_proof(relation or "", "MAX",
|
|
775
|
+
value_filter=value_filter,
|
|
776
|
+
user_id=user_id)
|
|
777
|
+
else:
|
|
778
|
+
return {"error": f"unknown query type {query!r}"}
|
|
779
|
+
verified = prover.verify(proof)
|
|
780
|
+
# Public view: only the claimed result + root + proof_id.
|
|
781
|
+
# The actual fact values are NOT in the public view (the
|
|
782
|
+
# serialized proof also does not contain them — see tests).
|
|
783
|
+
return {
|
|
784
|
+
"proof_id": proof.proof_id,
|
|
785
|
+
"query": proof.query,
|
|
786
|
+
"claimed_result": proof.claimed_result,
|
|
787
|
+
"merkle_root": proof.merkle_root[:32] + "…",
|
|
788
|
+
"n_facts_committed": proof.n_facts_committed,
|
|
789
|
+
"circuit_gates": proof.circuit_gates,
|
|
790
|
+
"verify": verified,
|
|
791
|
+
"transcript_size_bytes": len(proof.serialize()),
|
|
792
|
+
"n_matching": proof.transcript.get("n_matching"),
|
|
793
|
+
}
|
|
794
|
+
except Exception as e:
|
|
795
|
+
return {"query": query, "error": str(e)}
|
|
796
|
+
|
|
797
|
+
# ------------------------------------------------------------------
|
|
798
|
+
def _provenance_export(self, *, user_id: str | None = None,
|
|
799
|
+
valid_from: str | None = None,
|
|
800
|
+
valid_to: str | None = None,
|
|
801
|
+
include_hypotheses: bool = False,
|
|
802
|
+
submit_to_scitt: bool = True) -> dict:
|
|
803
|
+
"""W3C VC + COSE Sign1 + SCITT receipt export for a memory range.
|
|
804
|
+
|
|
805
|
+
Honors Config.provenance_enabled. When off, returns a disabled
|
|
806
|
+
stub explaining how to opt in.
|
|
807
|
+
"""
|
|
808
|
+
try:
|
|
809
|
+
if not getattr(self.memory.config, "provenance_enabled", False):
|
|
810
|
+
return {
|
|
811
|
+
"disabled": True,
|
|
812
|
+
"reason": "Config.provenance_enabled is False — set it "
|
|
813
|
+
"True to opt into standards-compliant provenance.",
|
|
814
|
+
"how_to_enable": "Set CONTEXT_M_PROVENANCE=true env var, "
|
|
815
|
+
"or Config(provenance_enabled=True).",
|
|
816
|
+
}
|
|
817
|
+
from cortexm.provenance import (
|
|
818
|
+
export_memory_range_vc, verify_vc,
|
|
819
|
+
sign_commit, verify_commit,
|
|
820
|
+
submit_to_scitt, verify_receipt,
|
|
821
|
+
get_default_agent, set_default_agent, Ed25519AgentKey,
|
|
822
|
+
)
|
|
823
|
+
|
|
824
|
+
# resolve the agent key
|
|
825
|
+
key_path = getattr(self.memory.config,
|
|
826
|
+
"provenance_agent_key_path", None)
|
|
827
|
+
if key_path:
|
|
828
|
+
agent = Ed25519AgentKey.from_pem(key_path)
|
|
829
|
+
set_default_agent(agent)
|
|
830
|
+
else:
|
|
831
|
+
agent = get_default_agent()
|
|
832
|
+
|
|
833
|
+
# 1. Export the memory range as a W3C VC
|
|
834
|
+
vc = export_memory_range_vc(
|
|
835
|
+
self.memory.store,
|
|
836
|
+
user_id=user_id,
|
|
837
|
+
valid_from=valid_from,
|
|
838
|
+
valid_to=valid_to,
|
|
839
|
+
include_hypotheses=include_hypotheses,
|
|
840
|
+
agent=agent)
|
|
841
|
+
vc_verify = verify_vc(vc, agent=agent)
|
|
842
|
+
|
|
843
|
+
# 2. Wrap a synthetic commit in a COSE Sign1 envelope
|
|
844
|
+
# (use the most recent commit on the active branch, or a
|
|
845
|
+
# synthetic commit id derived from the merkle root)
|
|
846
|
+
commit_id = vc.id.split(":")[-1]
|
|
847
|
+
chain_hash = vc.credential_subject.get("merkle_root", "")
|
|
848
|
+
n_facts = vc.credential_subject.get("n_facts", 0)
|
|
849
|
+
envelope = sign_commit(
|
|
850
|
+
commit_id=commit_id,
|
|
851
|
+
chain_hash=chain_hash,
|
|
852
|
+
n_facts=n_facts,
|
|
853
|
+
agent=agent,
|
|
854
|
+
extra_payload={
|
|
855
|
+
"user_id": user_id,
|
|
856
|
+
"valid_from": valid_from,
|
|
857
|
+
"valid_to": valid_to,
|
|
858
|
+
"vc_id": vc.id,
|
|
859
|
+
})
|
|
860
|
+
envelope_verify = verify_commit(
|
|
861
|
+
envelope, agent=agent,
|
|
862
|
+
expected_commit_id=commit_id,
|
|
863
|
+
expected_chain_hash=chain_hash)
|
|
864
|
+
|
|
865
|
+
# 3. Submit to SCITT transparency log
|
|
866
|
+
scitt = None
|
|
867
|
+
scitt_verify = None
|
|
868
|
+
if submit_to_scitt:
|
|
869
|
+
scitt = submit_to_scitt(envelope)
|
|
870
|
+
scitt_verify = verify_receipt(scitt)
|
|
871
|
+
|
|
872
|
+
return {
|
|
873
|
+
"vc": vc.to_dict(),
|
|
874
|
+
"vc_verify": vc_verify,
|
|
875
|
+
"cose_sign1": envelope.to_dict(),
|
|
876
|
+
"cose_verify": envelope_verify,
|
|
877
|
+
"scitt": (None if scitt is None else {
|
|
878
|
+
"service_did": scitt.service_did,
|
|
879
|
+
"leaf_hash": scitt.receipt.leaf_hash[:32] + "…",
|
|
880
|
+
"tree_size": scitt.receipt.tree_size,
|
|
881
|
+
"chain_head": scitt.receipt.chain_head[:32] + "…",
|
|
882
|
+
"ts": scitt.receipt.ts,
|
|
883
|
+
"verify": scitt_verify,
|
|
884
|
+
}),
|
|
885
|
+
"agent_did": agent.did,
|
|
886
|
+
"agent_label": agent.label,
|
|
887
|
+
"dev_mode": agent.dev_mode,
|
|
888
|
+
}
|
|
889
|
+
except Exception as e:
|
|
890
|
+
return {"error": str(e)}
|
|
891
|
+
|
|
892
|
+
# ------------------------------------------------------------------
|
|
893
|
+
def _structural_query(self, start_entity: str,
|
|
894
|
+
relation_chain: list[str],
|
|
895
|
+
user_id: str | None = None,
|
|
896
|
+
allow_hypotheses: bool = False,
|
|
897
|
+
vsa_fallback: bool = True) -> dict:
|
|
898
|
+
"""Deterministic multi-hop via symbolic Trace + VSA unbind."""
|
|
899
|
+
try:
|
|
900
|
+
if not start_entity or not relation_chain:
|
|
901
|
+
return {"error": "start_entity and relation_chain required"}
|
|
902
|
+
from cortexm.trace.structural import structural_query
|
|
903
|
+
res = structural_query(
|
|
904
|
+
self.memory.store,
|
|
905
|
+
self.memory.palace,
|
|
906
|
+
start_entity=start_entity,
|
|
907
|
+
relation_chain=relation_chain,
|
|
908
|
+
user_id=user_id,
|
|
909
|
+
allow_hypotheses=allow_hypotheses,
|
|
910
|
+
vsa_fallback=vsa_fallback)
|
|
911
|
+
return {
|
|
912
|
+
"start_entity": res.start_entity,
|
|
913
|
+
"relation_chain": res.relation_chain,
|
|
914
|
+
"final_value": res.final_value,
|
|
915
|
+
"success": res.success,
|
|
916
|
+
"confidence": res.confidence,
|
|
917
|
+
"failure_reason": res.failure_reason,
|
|
918
|
+
"hops": [
|
|
919
|
+
{"relation": h.relation, "subject": h.subject,
|
|
920
|
+
"value": h.value, "fact_id": h.fact_id,
|
|
921
|
+
"confidence": h.confidence, "via": h.via,
|
|
922
|
+
"ambiguous": h.ambiguous,
|
|
923
|
+
"alternatives": h.alternatives}
|
|
924
|
+
for h in res.hops
|
|
925
|
+
],
|
|
926
|
+
}
|
|
927
|
+
except Exception as e:
|
|
928
|
+
return {"error": str(e)}
|
|
929
|
+
|
|
930
|
+
# ------------------------------------------------------------------
|
|
931
|
+
def _cognition_run(self, user_id: str | None = None,
|
|
932
|
+
dry_run: bool = False) -> dict:
|
|
933
|
+
"""Run the HMS Cognition Engine pass on demand."""
|
|
934
|
+
try:
|
|
935
|
+
from cortexm.cognition import run_cognition_pass
|
|
936
|
+
report = run_cognition_pass(
|
|
937
|
+
self.memory.store, palace=self.memory.palace,
|
|
938
|
+
dry_run=dry_run, user_id=user_id)
|
|
939
|
+
return {
|
|
940
|
+
"scan": report.scan,
|
|
941
|
+
"abstraction": report.abstraction,
|
|
942
|
+
"gaps": report.gaps,
|
|
943
|
+
"hypotheses": report.hypotheses,
|
|
944
|
+
"analogies": report.analogies,
|
|
945
|
+
"total_derived_facts": report.total_derived_facts,
|
|
946
|
+
"duration_ms": report.duration_ms,
|
|
947
|
+
"cognition_commit_id": report.commit_id,
|
|
948
|
+
"dry_run": report.dry_run,
|
|
949
|
+
}
|
|
950
|
+
except Exception as e:
|
|
951
|
+
return {"error": str(e)}
|
|
952
|
+
|
|
953
|
+
# ------------------------------------------------------------------
|
|
954
|
+
@staticmethod
|
|
955
|
+
def _ok(req_id, result) -> dict:
|
|
956
|
+
return {"jsonrpc": "2.0", "id": req_id, "result": result}
|
|
957
|
+
|
|
958
|
+
@staticmethod
|
|
959
|
+
def _err(req_id, code, message) -> dict:
|
|
960
|
+
return {"jsonrpc": "2.0", "id": req_id,
|
|
961
|
+
"error": {"code": code, "message": message}}
|
|
962
|
+
|
|
963
|
+
|
|
964
|
+
def serve(db_path: str | None = None) -> None:
|
|
965
|
+
cfg = Config.from_env()
|
|
966
|
+
if db_path:
|
|
967
|
+
cfg.db_path = db_path
|
|
968
|
+
memory = Memory(cfg)
|
|
969
|
+
server = MCPServer(memory)
|
|
970
|
+
for line in sys.stdin:
|
|
971
|
+
line = line.strip()
|
|
972
|
+
if not line:
|
|
973
|
+
continue
|
|
974
|
+
try:
|
|
975
|
+
request = json.loads(line)
|
|
976
|
+
except json.JSONDecodeError:
|
|
977
|
+
continue
|
|
978
|
+
response = server.handle(request)
|
|
979
|
+
if response is not None:
|
|
980
|
+
sys.stdout.write(json.dumps(response) + "\n")
|
|
981
|
+
sys.stdout.flush()
|
|
982
|
+
|
|
983
|
+
|
|
984
|
+
if __name__ == "__main__":
|
|
985
|
+
serve()
|