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.
Files changed (120) hide show
  1. context_m.py +17 -0
  2. cortexm/__init__.py +45 -0
  3. cortexm/accel.py +403 -0
  4. cortexm/api/__init__.py +0 -0
  5. cortexm/api/chaos.py +118 -0
  6. cortexm/api/memory.py +635 -0
  7. cortexm/bench/__init__.py +0 -0
  8. cortexm/bench/abilities.py +311 -0
  9. cortexm/bench/baselines.py +89 -0
  10. cortexm/bench/beam_loader.py +317 -0
  11. cortexm/bench/generator.py +376 -0
  12. cortexm/bench/harness.py +211 -0
  13. cortexm/bench/messy.py +218 -0
  14. cortexm/bench/micro.py +251 -0
  15. cortexm/bench/ood.py +443 -0
  16. cortexm/bench/run.py +137 -0
  17. cortexm/bridge/__init__.py +0 -0
  18. cortexm/bridge/dates.py +178 -0
  19. cortexm/bridge/decoders.py +204 -0
  20. cortexm/bridge/enrich.py +255 -0
  21. cortexm/bridge/extractor.py +316 -0
  22. cortexm/bridge/fallback.py +332 -0
  23. cortexm/bridge/onnx_runtime.py +158 -0
  24. cortexm/bridge/patterns.py +760 -0
  25. cortexm/bridge/ppr.py +104 -0
  26. cortexm/bridge/prefilter.py +188 -0
  27. cortexm/bridge/query_extract.py +420 -0
  28. cortexm/bridge/reader.py +1174 -0
  29. cortexm/bridge/rerank.py +204 -0
  30. cortexm/bridge/writer.py +492 -0
  31. cortexm/cli.py +295 -0
  32. cortexm/cognition/__init__.py +53 -0
  33. cortexm/cognition/abstraction.py +192 -0
  34. cortexm/cognition/analogy.py +159 -0
  35. cortexm/cognition/engine.py +204 -0
  36. cortexm/cognition/gaps.py +365 -0
  37. cortexm/cognition/scanner.py +204 -0
  38. cortexm/config.py +375 -0
  39. cortexm/cortexm.py +8 -0
  40. cortexm/enterprise/__init__.py +0 -0
  41. cortexm/enterprise/audit.py +178 -0
  42. cortexm/enterprise/governance.py +239 -0
  43. cortexm/errors.py +35 -0
  44. cortexm/features/__init__.py +0 -0
  45. cortexm/features/git.py +204 -0
  46. cortexm/features/prefetch.py +88 -0
  47. cortexm/features/zk.py +105 -0
  48. cortexm/federation/__init__.py +39 -0
  49. cortexm/federation/crdt.py +275 -0
  50. cortexm/federation/fabric.py +109 -0
  51. cortexm/federation/hlc.py +80 -0
  52. cortexm/federation/node.py +145 -0
  53. cortexm/federation/schema_report.py +73 -0
  54. cortexm/federation/transport.py +164 -0
  55. cortexm/index/__init__.py +19 -0
  56. cortexm/index/nsg.py +386 -0
  57. cortexm/mcp/__init__.py +0 -0
  58. cortexm/mcp/server.py +985 -0
  59. cortexm/metrics.py +62 -0
  60. cortexm/migrate/__init__.py +0 -0
  61. cortexm/migrate/importers.py +192 -0
  62. cortexm/provenance/__init__.py +78 -0
  63. cortexm/provenance/agent.py +214 -0
  64. cortexm/provenance/cose.py +201 -0
  65. cortexm/provenance/scitt.py +258 -0
  66. cortexm/provenance/vc.py +250 -0
  67. cortexm/security/__init__.py +0 -0
  68. cortexm/security/crypto.py +162 -0
  69. cortexm/security/hashes.py +140 -0
  70. cortexm/security/injection.py +149 -0
  71. cortexm/security/mind.py +154 -0
  72. cortexm/security/pii.py +265 -0
  73. cortexm/security/rbac.py +169 -0
  74. cortexm/security/sandbox.py +131 -0
  75. cortexm/security/zk_hamming.py +142 -0
  76. cortexm/security/zk_sql.py +485 -0
  77. cortexm/server/__init__.py +0 -0
  78. cortexm/server/metrics.py +88 -0
  79. cortexm/server/rest.py +936 -0
  80. cortexm/server/sparql.py +984 -0
  81. cortexm/text/__init__.py +0 -0
  82. cortexm/text/dissim.py +252 -0
  83. cortexm/text/embedder.py +155 -0
  84. cortexm/text/fuzzy.py +218 -0
  85. cortexm/text/idiolect.py +253 -0
  86. cortexm/text/labse.py +374 -0
  87. cortexm/text/tokenizer.py +79 -0
  88. cortexm/trace/__init__.py +0 -0
  89. cortexm/trace/blob_arena.py +277 -0
  90. cortexm/trace/consolidate.py +337 -0
  91. cortexm/trace/contradictions.py +69 -0
  92. cortexm/trace/dedup.py +114 -0
  93. cortexm/trace/edges.py +214 -0
  94. cortexm/trace/fact.py +121 -0
  95. cortexm/trace/fade.py +245 -0
  96. cortexm/trace/lifecycle.py +112 -0
  97. cortexm/trace/rebuild.py +173 -0
  98. cortexm/trace/rules.py +171 -0
  99. cortexm/trace/store.py +680 -0
  100. cortexm/trace/structural.py +183 -0
  101. cortexm/trace/tmt.py +335 -0
  102. cortexm/util.py +148 -0
  103. cortexm/vsa/__init__.py +0 -0
  104. cortexm/vsa/attribution.py +149 -0
  105. cortexm/vsa/cleanup.py +161 -0
  106. cortexm/vsa/codecs.py +397 -0
  107. cortexm/vsa/hologram_overlay.py +139 -0
  108. cortexm/vsa/index.py +163 -0
  109. cortexm/vsa/ops.py +149 -0
  110. cortexm/vsa/palace.py +446 -0
  111. cortexm/vsa/role_vectors.py +236 -0
  112. cortexm/vsa/slb.py +78 -0
  113. cortexm/vsa/tlsh_trie.py +137 -0
  114. cortexm/vsa/working_memory.py +249 -0
  115. cortexm-0.3.0.dist-info/METADATA +482 -0
  116. cortexm-0.3.0.dist-info/RECORD +120 -0
  117. cortexm-0.3.0.dist-info/WHEEL +5 -0
  118. cortexm-0.3.0.dist-info/entry_points.txt +2 -0
  119. cortexm-0.3.0.dist-info/licenses/LICENSE +190 -0
  120. 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()