structuremappingmemory 1.0.0__tar.gz → 1.1.0__tar.gz

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 (138) hide show
  1. {structuremappingmemory-1.0.0/structuremappingmemory.egg-info → structuremappingmemory-1.1.0}/PKG-INFO +17 -8
  2. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/README.md +14 -7
  3. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/pyproject.toml +5 -1
  4. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/__init__.py +1 -1
  5. structuremappingmemory-1.1.0/sma/mcp.py +393 -0
  6. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0/structuremappingmemory.egg-info}/PKG-INFO +17 -8
  7. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/structuremappingmemory.egg-info/SOURCES.txt +2 -0
  8. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/structuremappingmemory.egg-info/entry_points.txt +1 -0
  9. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/structuremappingmemory.egg-info/requires.txt +3 -0
  10. structuremappingmemory-1.1.0/tests/test_mcp.py +138 -0
  11. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/LICENSE +0 -0
  12. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/setup.cfg +0 -0
  13. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/__main__.py +0 -0
  14. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/agent/__init__.py +0 -0
  15. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/agent/adapter_draft.py +0 -0
  16. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/agent/api.py +0 -0
  17. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/agent/comparison.py +0 -0
  18. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/agent/llm.py +0 -0
  19. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/agent/policies.py +0 -0
  20. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/agent/service.py +0 -0
  21. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/cli.py +0 -0
  22. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/encoders/__init__.py +0 -0
  23. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/encoders/agentobs.py +0 -0
  24. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/encoders/base.py +0 -0
  25. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/encoders/code_treesitter.py +0 -0
  26. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/encoders/coverage.py +0 -0
  27. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/encoders/draft_adapter.py +0 -0
  28. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/encoders/healthcare.py +0 -0
  29. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/encoders/logs_drain.py +0 -0
  30. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/encoders/prose_tier1.py +0 -0
  31. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/encoders/structured.py +0 -0
  32. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/encoders/traces.py +0 -0
  33. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/__init__.py +0 -0
  34. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/agentic/__init__.py +0 -0
  35. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/agentic/arms/__init__.py +0 -0
  36. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/agentic/arms/cyber.py +0 -0
  37. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/agentic/arms/discovery.py +0 -0
  38. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/agentic/arms/finance.py +0 -0
  39. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/agentic/arms/legal.py +0 -0
  40. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/agentic/arms/medicine.py +0 -0
  41. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/agentic/harness.py +0 -0
  42. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/agentic/memories.py +0 -0
  43. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/agentic/metrics.py +0 -0
  44. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/agentic_qa/__init__.py +0 -0
  45. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/agentic_qa/agent.py +0 -0
  46. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/agentic_qa/metrics.py +0 -0
  47. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/agentic_qa/pools.py +0 -0
  48. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/arn.py +0 -0
  49. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/baselines/__init__.py +0 -0
  50. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/baselines/bge_dense.py +0 -0
  51. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/baselines/bm25.py +0 -0
  52. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/baselines/dense.py +0 -0
  53. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/baselines/hipporag.py +0 -0
  54. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/baselines/hybrid_rrf.py +0 -0
  55. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/baselines/longcontext_llm.py +0 -0
  56. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/baselines/rerank.py +0 -0
  57. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/baselines/splade.py +0 -0
  58. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/baselines/wl_kernel.py +0 -0
  59. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/bugsinpy.py +0 -0
  60. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/bugsinpy_families.py +0 -0
  61. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/crossdomain.py +0 -0
  62. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/diabetes.py +0 -0
  63. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/drift_env.py +0 -0
  64. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/drift_metrics.py +0 -0
  65. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/family_labels.py +0 -0
  66. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/fraud_elliptic/__init__.py +0 -0
  67. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/fraud_elliptic/encoder.py +0 -0
  68. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/fraud_elliptic/eval.py +0 -0
  69. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/fraud_elliptic/test_encoder.py +0 -0
  70. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/ieee_cis.py +0 -0
  71. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/loghub.py +0 -0
  72. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/loghub_eval.py +0 -0
  73. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/longmemeval.py +0 -0
  74. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/memory_backends/__init__.py +0 -0
  75. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/memory_backends/base.py +0 -0
  76. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/memory_backends/context_only.py +0 -0
  77. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/memory_backends/rag_notes.py +0 -0
  78. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/memory_backends/shared_llm.py +0 -0
  79. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/memory_backends/sma_memory.py +0 -0
  80. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/memory_backends/zep_graphiti.py +0 -0
  81. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/metrics.py +0 -0
  82. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/ontology_bench.py +0 -0
  83. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/report.py +0 -0
  84. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/ssb_eval.py +0 -0
  85. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/ssb_generator.py +0 -0
  86. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/stats.py +0 -0
  87. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/transfer_eval.py +0 -0
  88. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/index/__init__.py +0 -0
  89. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/index/ann.py +0 -0
  90. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/index/content_vectors.py +0 -0
  91. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/index/inverted.py +0 -0
  92. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/index/macfac.py +0 -0
  93. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ir/__init__.py +0 -0
  94. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ir/canon.py +0 -0
  95. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ir/schema.py +0 -0
  96. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ir/sexpr.py +0 -0
  97. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ir/signatures.py +0 -0
  98. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/match/__init__.py +0 -0
  99. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/match/conflicts.py +0 -0
  100. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/match/engine.py +0 -0
  101. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/match/explain.py +0 -0
  102. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/match/infer.py +0 -0
  103. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/match/kernels.py +0 -0
  104. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/match/mdl.py +0 -0
  105. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/match/merge_cpsat.py +0 -0
  106. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/match/merge_greedy.py +0 -0
  107. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/match/mh.py +0 -0
  108. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/match/ses.py +0 -0
  109. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/match/types.py +0 -0
  110. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/match/verifier.py +0 -0
  111. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ontology/__init__.py +0 -0
  112. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ontology/attack.py +0 -0
  113. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ontology/cpc.py +0 -0
  114. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ontology/graph.py +0 -0
  115. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ontology/loader.py +0 -0
  116. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ontology/mitre_xml.py +0 -0
  117. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ontology/mount.py +0 -0
  118. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ontology/rdf_loader.py +0 -0
  119. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ontology/registry.py +0 -0
  120. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ontology/router.py +0 -0
  121. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ontology/usgaap.py +0 -0
  122. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/sage/__init__.py +0 -0
  123. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/sage/assimilate.py +0 -0
  124. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/sage/pools.py +0 -0
  125. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/sage/probabilities.py +0 -0
  126. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/store/__init__.py +0 -0
  127. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/store/lmdb_store.py +0 -0
  128. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/store/registry.py +0 -0
  129. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/store/wal.py +0 -0
  130. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ui/app.py +0 -0
  131. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/structuremappingmemory.egg-info/dependency_links.txt +0 -0
  132. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/structuremappingmemory.egg-info/top_level.txt +0 -0
  133. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/tests/test_bugsinpy_t3.py +0 -0
  134. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/tests/test_drift.py +0 -0
  135. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/tests/test_gates.py +0 -0
  136. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/tests/test_hipporag.py +0 -0
  137. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/tests/test_production_loop.py +0 -0
  138. {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/tests/test_stats.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: structuremappingmemory
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: SMA-1: Structure-Mapping Agentic Memory — a structure-mapping retrieval memory that grounds language models in curated ontologies
5
5
  Author-email: Ayaz Khan <aak2259@columbia.edu>
6
6
  License: Apache-2.0
@@ -50,11 +50,14 @@ Requires-Dist: networkx>=3; extra == "eval"
50
50
  Provides-Extra: local-llm
51
51
  Requires-Dist: huggingface-hub>=0.23; extra == "local-llm"
52
52
  Requires-Dist: llama-cpp-python>=0.2.90; extra == "local-llm"
53
+ Provides-Extra: mcp
54
+ Requires-Dist: mcp>=1.0; extra == "mcp"
53
55
  Dynamic: license-file
54
56
 
55
57
  # SMA-1: Structure-Mapping Agentic Memory
56
58
 
57
59
  [![CI](https://github.com/ayazkhan27/SMA-1/actions/workflows/ci.yml/badge.svg)](https://github.com/ayazkhan27/SMA-1/actions/workflows/ci.yml)
60
+ [![PyPI](https://img.shields.io/pypi/v/structuremappingmemory)](https://pypi.org/project/structuremappingmemory/)
58
61
  [![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
59
62
  [![HF Space](https://img.shields.io/badge/%F0%9F%A4%97%20Demo-Space-orange)](https://huggingface.co/spaces/zephyr27/SMA-1-demo)
60
63
 
@@ -103,13 +106,13 @@ adapters.
103
106
  On a **memory-swap benchmark** where only the retriever varies, SMA beats a strong
104
107
  RAG + KG baseline suite on the rare/long-tail slice across five domains:
105
108
 
106
- | Domain | SMA tail top-5 | best RAG | Δ | Holm-significant |
107
- |---|:--:|:--:|:--:|:--:|
108
- | Medicine (HPO/MONDO) | 0.949 | 0.606 | **+0.333** | yes |
109
- | Finance (US-GAAP) | 0.418 | 0.231 | **+0.167** | yes |
110
- | Genomics (GO) | 0.849 | 0.682 | **+0.156** | yes |
111
- | Legal (CPC, all-query) | 0.941 | 0.870 | **+0.064** | yes |
112
- | Cyber (ATT&CK) | 0.766 | 0.749 | +0.073 | directional* |
109
+ | Domain | SMA tail top-5 | best RAG | Δ tail top-5 | Cliff's δ | Holm-significant |
110
+ |---|:--:|:--:|:--:|:--:|:--:|
111
+ | Medicine (HPO/MONDO) | 0.949 | 0.606 | **+0.343** | 0.333 | yes |
112
+ | Finance (US-GAAP) | 0.418 | 0.231 | **+0.187** | 0.167 | yes |
113
+ | Genomics (GO) | 0.849 | 0.682 | **+0.167** | 0.156 | yes |
114
+ | Legal (CPC, all-query) | 0.941 | 0.870 | **+0.071** | 0.064 | yes |
115
+ | Cyber (ATT&CK) | 0.766 | 0.749 | +0.017 | 0.073 | directional* |
113
116
 
114
117
  \* Cyber survives Holm across domains but not a conservative
115
118
  Bonferroni-over-baselines selection correction (p=0.035 → 0.17); reported as
@@ -142,6 +145,12 @@ logistic-regression baseline wins. The advantage is specific to structure.
142
145
 
143
146
  ## Quick start
144
147
 
148
+ ```bash
149
+ pip install structuremappingmemory # from PyPI (import name: sma)
150
+ ```
151
+
152
+ Or for development, with the evaluation/encoder extras:
153
+
145
154
  ```bash
146
155
  python -m venv .venv && . .venv/bin/activate
147
156
  pip install -e ".[encoders,eval]"
@@ -1,6 +1,7 @@
1
1
  # SMA-1: Structure-Mapping Agentic Memory
2
2
 
3
3
  [![CI](https://github.com/ayazkhan27/SMA-1/actions/workflows/ci.yml/badge.svg)](https://github.com/ayazkhan27/SMA-1/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/structuremappingmemory)](https://pypi.org/project/structuremappingmemory/)
4
5
  [![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
5
6
  [![HF Space](https://img.shields.io/badge/%F0%9F%A4%97%20Demo-Space-orange)](https://huggingface.co/spaces/zephyr27/SMA-1-demo)
6
7
 
@@ -49,13 +50,13 @@ adapters.
49
50
  On a **memory-swap benchmark** where only the retriever varies, SMA beats a strong
50
51
  RAG + KG baseline suite on the rare/long-tail slice across five domains:
51
52
 
52
- | Domain | SMA tail top-5 | best RAG | Δ | Holm-significant |
53
- |---|:--:|:--:|:--:|:--:|
54
- | Medicine (HPO/MONDO) | 0.949 | 0.606 | **+0.333** | yes |
55
- | Finance (US-GAAP) | 0.418 | 0.231 | **+0.167** | yes |
56
- | Genomics (GO) | 0.849 | 0.682 | **+0.156** | yes |
57
- | Legal (CPC, all-query) | 0.941 | 0.870 | **+0.064** | yes |
58
- | Cyber (ATT&CK) | 0.766 | 0.749 | +0.073 | directional* |
53
+ | Domain | SMA tail top-5 | best RAG | Δ tail top-5 | Cliff's δ | Holm-significant |
54
+ |---|:--:|:--:|:--:|:--:|:--:|
55
+ | Medicine (HPO/MONDO) | 0.949 | 0.606 | **+0.343** | 0.333 | yes |
56
+ | Finance (US-GAAP) | 0.418 | 0.231 | **+0.187** | 0.167 | yes |
57
+ | Genomics (GO) | 0.849 | 0.682 | **+0.167** | 0.156 | yes |
58
+ | Legal (CPC, all-query) | 0.941 | 0.870 | **+0.071** | 0.064 | yes |
59
+ | Cyber (ATT&CK) | 0.766 | 0.749 | +0.017 | 0.073 | directional* |
59
60
 
60
61
  \* Cyber survives Holm across domains but not a conservative
61
62
  Bonferroni-over-baselines selection correction (p=0.035 → 0.17); reported as
@@ -88,6 +89,12 @@ logistic-regression baseline wins. The advantage is specific to structure.
88
89
 
89
90
  ## Quick start
90
91
 
92
+ ```bash
93
+ pip install structuremappingmemory # from PyPI (import name: sma)
94
+ ```
95
+
96
+ Or for development, with the evaluation/encoder extras:
97
+
91
98
  ```bash
92
99
  python -m venv .venv && . .venv/bin/activate
93
100
  pip install -e ".[encoders,eval]"
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  # PyPI distribution name (the import name stays `sma`); `sma` is taken on PyPI.
7
7
  name = "structuremappingmemory"
8
- version = "1.0.0"
8
+ version = "1.1.0"
9
9
  description = "SMA-1: Structure-Mapping Agentic Memory — a structure-mapping retrieval memory that grounds language models in curated ontologies"
10
10
  readme = "README.md"
11
11
  requires-python = ">=3.10"
@@ -60,6 +60,9 @@ local-llm = [
60
60
  "huggingface-hub>=0.23",
61
61
  "llama-cpp-python>=0.2.90"
62
62
  ]
63
+ mcp = [
64
+ "mcp>=1.0"
65
+ ]
63
66
 
64
67
  [project.urls]
65
68
  Homepage = "https://github.com/ayazkhan27/SMA-1"
@@ -68,6 +71,7 @@ Issues = "https://github.com/ayazkhan27/SMA-1/issues"
68
71
 
69
72
  [project.scripts]
70
73
  sma = "sma.cli:main"
74
+ sma-mcp = "sma.mcp:main"
71
75
 
72
76
  [tool.setuptools.packages.find]
73
77
  include = ["sma*"]
@@ -1,5 +1,5 @@
1
1
  """SMA-1: Structure-Mapping Agentic Memory MVP."""
2
2
 
3
3
  __all__ = ["__version__"]
4
- __version__ = "0.1.0"
4
+ __version__ = "1.1.0"
5
5
 
@@ -0,0 +1,393 @@
1
+ """SMA-1 Model Context Protocol (MCP) server.
2
+
3
+ Exposes the structure-mapping memory as MCP tools so an MCP client (Codex CLI,
4
+ Claude, the OpenAI Agents SDK, ...) can:
5
+
6
+ * mount curated ontologies (one shared core, many domains),
7
+ * index a case base (records expressed as ontology term-ids),
8
+ * retrieve *structurally-analogous* prior cases by logical structure
9
+ (is-a subsumption + typed relations + rarity weighting), with a checkable
10
+ structural citation, a cite-or-abstain decision, and an expectation-violation
11
+ **novelty** flag.
12
+
13
+ This is the analogical-memory layer for a discovery loop: the LLM generates and
14
+ verifies; SMA grounds each step in structurally-analogous precedent and flags the
15
+ genuinely never-seen, which surface-similarity (vector RAG) cannot do.
16
+
17
+ Design notes
18
+ ------------
19
+ * The engine (:class:`SmaEngine`) is import-light: it uses only the SMA core
20
+ (``sma.ontology`` / ``sma.index`` / ``sma.sage``), NOT the eval baselines
21
+ (bm25 / dense / hipporag) and NOT the ``mcp`` SDK -- so it is unit-testable
22
+ without the transport dependency. ``mcp`` is imported lazily in
23
+ :func:`build_server` / :func:`main`.
24
+ * Cite-or-abstain gates on the RAW structural grounding score (the codebase is
25
+ explicit that the normalized confidence saturates and does not separate
26
+ known/unknown). A per-ontology ``ground_threshold`` is calibrated offline; when
27
+ unset there is no gate (every non-empty result is "grounded") -- calibrate it.
28
+
29
+ Run as a stdio MCP server::
30
+
31
+ python -m sma.mcp # or: sma-mcp
32
+
33
+ Configure a manifest of ontologies to auto-register via the ``SMA_MANIFEST`` env
34
+ var (see ``examples/sma_manifest.example.json``).
35
+ """
36
+ from __future__ import annotations
37
+
38
+ import json
39
+ import os
40
+ from dataclasses import dataclass, field
41
+ from typing import Any
42
+
43
+ from sma.index.macfac import MacFacIndex
44
+ from sma.ontology import (
45
+ MountedOntology,
46
+ load_attack_stix,
47
+ load_capec,
48
+ load_cpc,
49
+ load_cwe,
50
+ load_mitre_xml,
51
+ load_obo,
52
+ load_ontology,
53
+ load_owl,
54
+ load_owl_dir,
55
+ load_rdflib,
56
+ load_usgaap,
57
+ mount,
58
+ )
59
+ from sma.sage.pools import SagePool
60
+
61
+ # Format -> loader. "auto" dispatches obo/owl by file extension.
62
+ _LOADERS = {
63
+ "auto": load_ontology,
64
+ "obo": load_obo,
65
+ "owl": load_owl,
66
+ "owl_dir": load_owl_dir,
67
+ "rdf": load_rdflib,
68
+ "ttl": load_rdflib,
69
+ "stix": load_attack_stix,
70
+ "attack": load_attack_stix,
71
+ "cpc": load_cpc,
72
+ "xbrl": load_usgaap,
73
+ "usgaap": load_usgaap,
74
+ "cwe": load_cwe,
75
+ "capec": load_capec,
76
+ "mitre_xml": load_mitre_xml,
77
+ }
78
+
79
+ SUPPORTED_FORMATS = sorted(_LOADERS)
80
+
81
+ _SENTINEL = object()
82
+
83
+
84
+ def _load_graph(path: str, fmt: str | None):
85
+ fmt = (fmt or "auto").lower()
86
+ loader = _LOADERS.get(fmt)
87
+ if loader is None:
88
+ raise ValueError(f"unknown ontology format {fmt!r}; supported: {SUPPORTED_FORMATS}")
89
+ return loader(path)
90
+
91
+
92
+ def _envfloat(name: str, default: float | None) -> float | None:
93
+ val = os.environ.get(name)
94
+ if val is None or val == "":
95
+ return default
96
+ try:
97
+ return float(val)
98
+ except ValueError:
99
+ return default
100
+
101
+
102
+ @dataclass
103
+ class _Ont:
104
+ """A registered ontology and (lazily) its mounted lattice + case index."""
105
+
106
+ name: str
107
+ path: str
108
+ fmt: str = "auto"
109
+ ground_threshold: float | None = None
110
+ novelty_threshold: float = 0.5
111
+ mounted: MountedOntology | None = None
112
+ cases: list[dict] = field(default_factory=list)
113
+ _index: Any = None
114
+ _pool: Any = None
115
+ _keymap: dict[str, str] = field(default_factory=dict)
116
+
117
+
118
+ class SmaEngine:
119
+ """The MCP-free core: register/mount ontologies, index cases, retrieve, novelty.
120
+
121
+ Mirrors ``sma.eval.agentic.memories.SmaMemory`` (build_case + MacFacIndex +
122
+ SagePool) without importing the eval module's heavy baseline dependencies.
123
+ """
124
+
125
+ def __init__(self) -> None:
126
+ self.onts: dict[str, _Ont] = {}
127
+ self.default_ground_threshold = _envfloat("SMA_GROUND_THRESHOLD", None)
128
+ self.default_novelty_threshold = _envfloat("SMA_NOVELTY_THRESHOLD", 0.5) or 0.5
129
+
130
+ # -- registration / mounting -------------------------------------------
131
+ def register(
132
+ self,
133
+ name: str,
134
+ path: str,
135
+ fmt: str = "auto",
136
+ ground_threshold: float | None = None,
137
+ novelty_threshold: float | None = None,
138
+ ) -> None:
139
+ self.onts[name] = _Ont(
140
+ name=name,
141
+ path=path,
142
+ fmt=(fmt or "auto"),
143
+ ground_threshold=(
144
+ ground_threshold if ground_threshold is not None else self.default_ground_threshold
145
+ ),
146
+ novelty_threshold=(
147
+ novelty_threshold if novelty_threshold is not None else self.default_novelty_threshold
148
+ ),
149
+ )
150
+
151
+ def _get(self, name: str) -> _Ont:
152
+ o = self.onts.get(name)
153
+ if o is None:
154
+ raise KeyError(f"ontology {name!r} is not registered; mount it first ({sorted(self.onts)})")
155
+ return o
156
+
157
+ def _ensure_mounted(self, name: str) -> _Ont:
158
+ o = self._get(name)
159
+ if o.mounted is None:
160
+ o.mounted = mount(_load_graph(o.path, o.fmt))
161
+ return o
162
+
163
+ def mount_ontology(self, name: str, path: str, fmt: str = "auto") -> dict:
164
+ self.register(name, path, fmt)
165
+ o = self._ensure_mounted(name)
166
+ return {
167
+ "ontology": name,
168
+ "format": o.fmt,
169
+ "concepts": len(o.mounted.graph.terms),
170
+ "indexed_cases": len(o.cases),
171
+ }
172
+
173
+ # -- indexing -----------------------------------------------------------
174
+ def _rebuild(self, o: _Ont) -> None:
175
+ o._keymap = {}
176
+ o._pool = SagePool("mcp", assimilation_threshold=0.2)
177
+ cases = []
178
+ for c in o.cases:
179
+ case = o.mounted.build_case(frozenset(c["term_ids"]), metadata={"key": c["key"]})
180
+ o._keymap[case.case_id] = c["key"]
181
+ cases.append(case)
182
+ o._pool.assimilate(case)
183
+ o._index = MacFacIndex(config=o.mounted.config, canon=o.mounted.canon)
184
+ o._index.build(cases)
185
+
186
+ def index_cases(self, name: str, cases: list[dict]) -> int:
187
+ """Append cases (each ``{key, term_ids, text?}``) and (re)build the index."""
188
+ o = self._ensure_mounted(name)
189
+ for c in cases:
190
+ if "key" not in c or "term_ids" not in c:
191
+ raise ValueError("each case needs 'key' and 'term_ids'")
192
+ o.cases.append({"key": str(c["key"]), "term_ids": list(c["term_ids"]), "text": c.get("text", "")})
193
+ self._rebuild(o)
194
+ return len(o.cases)
195
+
196
+ # -- encoding (no LLM): raw text -> ontology term-ids -------------------
197
+ def encode_text(self, name: str, text: str, max_terms: int = 20) -> dict:
198
+ """Deterministic lexical match of term NAMES in ``text`` -> term-ids.
199
+
200
+ A starter encoder (no LLM, honoring the no-LLM-in-extraction principle).
201
+ Replace with a domain-specific encoder for production recall.
202
+ """
203
+ o = self._ensure_mounted(name)
204
+ hay = f" {text.lower()} "
205
+ hits: list[tuple[str, str]] = []
206
+ for tid, term in o.mounted.graph.terms.items():
207
+ nm = (getattr(term, "name", "") or "").strip().lower()
208
+ if len(nm) >= 3 and f" {nm} " in hay:
209
+ hits.append((tid, term.name))
210
+ hits.sort(key=lambda h: -len(h[1])) # prefer more specific (longer) names
211
+ hits = hits[:max_terms]
212
+ return {"ontology": name, "term_ids": [h[0] for h in hits], "matched_names": [h[1] for h in hits]}
213
+
214
+ def _resolve_terms(self, o: _Ont, term_ids, text) -> frozenset[str]:
215
+ if term_ids:
216
+ return frozenset(term_ids)
217
+ if text:
218
+ return frozenset(self.encode_text(o.name, text)["term_ids"])
219
+ raise ValueError("provide either term_ids or text")
220
+
221
+ # -- retrieval / novelty ------------------------------------------------
222
+ def retrieve(
223
+ self,
224
+ name: str,
225
+ term_ids=None,
226
+ text: str = "",
227
+ k: int = 5,
228
+ ground_threshold=_SENTINEL,
229
+ ) -> dict:
230
+ o = self._ensure_mounted(name)
231
+ tids = self._resolve_terms(o, term_ids, text)
232
+ if not tids:
233
+ return {"ontology": name, "abstain": True, "reason": "no query term-ids (encoding matched nothing)",
234
+ "citations": [], "novelty": 1.0, "novelty_flag": True, "query_term_ids": []}
235
+ if o._index is None:
236
+ return {"ontology": name, "abstain": True, "reason": "no cases indexed",
237
+ "citations": [], "novelty": 1.0, "novelty_flag": True, "query_term_ids": sorted(tids)}
238
+ qc = o.mounted.build_case(tids)
239
+ res = o._index.retrieve(qc, k=k, shortlist=80, fac_budget=40)
240
+ nov = float(o._pool.expectation_violation(qc))
241
+ thr = o.ground_threshold if ground_threshold is _SENTINEL else ground_threshold
242
+ top = max((r.score for r in res), default=0.0) or 1.0
243
+ citations = [
244
+ {
245
+ "id": o._keymap.get(r.case_id, ""),
246
+ "score": round(float(r.score), 4),
247
+ "confidence": round(min(max(r.score / top, 0.0), 1.0), 4),
248
+ "rank": i,
249
+ }
250
+ for i, r in enumerate(res, 1)
251
+ ]
252
+ grounded = bool(res) and (thr is None or res[0].score >= thr)
253
+ return {
254
+ "ontology": name,
255
+ "query_term_ids": sorted(tids),
256
+ "abstain": not grounded,
257
+ "abstain_threshold": thr,
258
+ "novelty": round(nov, 4),
259
+ "novelty_flag": nov >= o.novelty_threshold,
260
+ "citations": citations if grounded else [],
261
+ "note": (
262
+ None if grounded
263
+ else "top grounding score below threshold -> no structural precedent; do not fabricate an answer"
264
+ ),
265
+ }
266
+
267
+ def novelty(self, name: str, term_ids=None, text: str = "") -> dict:
268
+ o = self._ensure_mounted(name)
269
+ tids = self._resolve_terms(o, term_ids, text)
270
+ if o._pool is None or not tids:
271
+ return {"ontology": name, "novelty": 1.0, "novelty_flag": True, "query_term_ids": sorted(tids)}
272
+ qc = o.mounted.build_case(tids)
273
+ nov = float(o._pool.expectation_violation(qc))
274
+ return {
275
+ "ontology": name,
276
+ "query_term_ids": sorted(tids),
277
+ "novelty": round(nov, 4),
278
+ "novelty_flag": nov >= o.novelty_threshold,
279
+ }
280
+
281
+ def list_ontologies(self) -> dict:
282
+ return {
283
+ "ontologies": [
284
+ {
285
+ "name": o.name,
286
+ "format": o.fmt,
287
+ "mounted": o.mounted is not None,
288
+ "concepts": (len(o.mounted.graph.terms) if o.mounted is not None else None),
289
+ "indexed_cases": len(o.cases),
290
+ "ground_threshold": o.ground_threshold,
291
+ "novelty_threshold": o.novelty_threshold,
292
+ }
293
+ for o in self.onts.values()
294
+ ],
295
+ "supported_formats": SUPPORTED_FORMATS,
296
+ }
297
+
298
+
299
+ def load_manifest(engine: SmaEngine, path: str) -> None:
300
+ """Register ontologies (and optionally eager-index cases) from a JSON manifest.
301
+
302
+ ``{"ontologies": [{"name","path","format"?,"ground_threshold"?,"novelty_threshold"?}],
303
+ "cases": {"<ontology>": [{"key","term_ids","text"?}, ...]}}``
304
+ """
305
+ with open(path) as fh:
306
+ data = json.load(fh)
307
+ for o in data.get("ontologies", []):
308
+ engine.register(
309
+ o["name"], o["path"], o.get("format", "auto"),
310
+ o.get("ground_threshold"), o.get("novelty_threshold"),
311
+ )
312
+ for name, cases in (data.get("cases") or {}).items():
313
+ if cases:
314
+ engine.index_cases(name, cases)
315
+
316
+
317
+ def build_server(engine: SmaEngine | None = None):
318
+ """Construct the FastMCP server (imports the ``mcp`` SDK lazily)."""
319
+ from mcp.server.fastmcp import FastMCP
320
+
321
+ engine = engine or SmaEngine()
322
+ manifest = os.environ.get("SMA_MANIFEST")
323
+ if manifest and os.path.exists(manifest):
324
+ load_manifest(engine, manifest)
325
+
326
+ server = FastMCP("sma-1")
327
+
328
+ @server.tool()
329
+ def list_ontologies() -> dict:
330
+ """List registered ontologies, their concept counts, indexed-case counts, and supported source formats."""
331
+ return engine.list_ontologies()
332
+
333
+ @server.tool()
334
+ def mount_ontology(name: str, path: str, format: str = "auto") -> dict:
335
+ """Register and mount a curated ontology as a structural lattice.
336
+
337
+ `format` is auto-detected for OBO/OWL; pass one of the supported formats
338
+ (stix, cpc, xbrl, cwe, capec, mitre_xml, rdf, ...) otherwise. Returns the
339
+ concept count once mounted."""
340
+ return engine.mount_ontology(name, path, format)
341
+
342
+ @server.tool()
343
+ def index_cases(ontology: str, cases: list[dict]) -> dict:
344
+ """Add cases to an ontology's memory. Each case is {"key","term_ids",["text"]}.
345
+
346
+ `key` is what gets cited back; `term_ids` are the ontology term-ids that
347
+ encode the case's structure. Returns the total number of indexed cases."""
348
+ total = engine.index_cases(ontology, cases)
349
+ return {"ontology": ontology, "indexed_cases": total}
350
+
351
+ @server.tool()
352
+ def encode_text(ontology: str, text: str, max_terms: int = 20) -> dict:
353
+ """Deterministically map free text to ontology term-ids via term-name matching (no LLM).
354
+
355
+ Use to turn an abstract/finding into `term_ids` before retrieve/novelty.
356
+ This is a starter encoder; swap in a domain encoder for better recall."""
357
+ return engine.encode_text(ontology, text, max_terms)
358
+
359
+ @server.tool()
360
+ def retrieve(
361
+ ontology: str,
362
+ text: str = "",
363
+ term_ids: list[str] | None = None,
364
+ k: int = 5,
365
+ ground_threshold: float | None = None,
366
+ ) -> dict:
367
+ """Retrieve STRUCTURALLY-ANALOGOUS prior cases by logical structure (not surface similarity).
368
+
369
+ Provide `term_ids` (preferred) or `text` (encoded on the fly). Returns ranked
370
+ citations {id, score, confidence, rank}, a calibrated cite-or-abstain decision
371
+ (`abstain`=True means no structural precedent -- do NOT fabricate), and a
372
+ `novelty` score (high = a case unlike anything indexed)."""
373
+ gt = _SENTINEL if ground_threshold is None else ground_threshold
374
+ return engine.retrieve(ontology, term_ids=term_ids, text=text, k=k, ground_threshold=gt)
375
+
376
+ @server.tool()
377
+ def novelty(ontology: str, text: str = "", term_ids: list[str] | None = None) -> dict:
378
+ """Score how novel a case is vs. the indexed memory (expectation-violation).
379
+
380
+ High `novelty` / `novelty_flag`=True means the case is structurally unlike
381
+ anything seen -- a candidate genuinely-new pattern."""
382
+ return engine.novelty(ontology, term_ids=term_ids, text=text)
383
+
384
+ return server, engine
385
+
386
+
387
+ def main() -> None:
388
+ server, _ = build_server()
389
+ server.run()
390
+
391
+
392
+ if __name__ == "__main__":
393
+ main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: structuremappingmemory
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: SMA-1: Structure-Mapping Agentic Memory — a structure-mapping retrieval memory that grounds language models in curated ontologies
5
5
  Author-email: Ayaz Khan <aak2259@columbia.edu>
6
6
  License: Apache-2.0
@@ -50,11 +50,14 @@ Requires-Dist: networkx>=3; extra == "eval"
50
50
  Provides-Extra: local-llm
51
51
  Requires-Dist: huggingface-hub>=0.23; extra == "local-llm"
52
52
  Requires-Dist: llama-cpp-python>=0.2.90; extra == "local-llm"
53
+ Provides-Extra: mcp
54
+ Requires-Dist: mcp>=1.0; extra == "mcp"
53
55
  Dynamic: license-file
54
56
 
55
57
  # SMA-1: Structure-Mapping Agentic Memory
56
58
 
57
59
  [![CI](https://github.com/ayazkhan27/SMA-1/actions/workflows/ci.yml/badge.svg)](https://github.com/ayazkhan27/SMA-1/actions/workflows/ci.yml)
60
+ [![PyPI](https://img.shields.io/pypi/v/structuremappingmemory)](https://pypi.org/project/structuremappingmemory/)
58
61
  [![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
59
62
  [![HF Space](https://img.shields.io/badge/%F0%9F%A4%97%20Demo-Space-orange)](https://huggingface.co/spaces/zephyr27/SMA-1-demo)
60
63
 
@@ -103,13 +106,13 @@ adapters.
103
106
  On a **memory-swap benchmark** where only the retriever varies, SMA beats a strong
104
107
  RAG + KG baseline suite on the rare/long-tail slice across five domains:
105
108
 
106
- | Domain | SMA tail top-5 | best RAG | Δ | Holm-significant |
107
- |---|:--:|:--:|:--:|:--:|
108
- | Medicine (HPO/MONDO) | 0.949 | 0.606 | **+0.333** | yes |
109
- | Finance (US-GAAP) | 0.418 | 0.231 | **+0.167** | yes |
110
- | Genomics (GO) | 0.849 | 0.682 | **+0.156** | yes |
111
- | Legal (CPC, all-query) | 0.941 | 0.870 | **+0.064** | yes |
112
- | Cyber (ATT&CK) | 0.766 | 0.749 | +0.073 | directional* |
109
+ | Domain | SMA tail top-5 | best RAG | Δ tail top-5 | Cliff's δ | Holm-significant |
110
+ |---|:--:|:--:|:--:|:--:|:--:|
111
+ | Medicine (HPO/MONDO) | 0.949 | 0.606 | **+0.343** | 0.333 | yes |
112
+ | Finance (US-GAAP) | 0.418 | 0.231 | **+0.187** | 0.167 | yes |
113
+ | Genomics (GO) | 0.849 | 0.682 | **+0.167** | 0.156 | yes |
114
+ | Legal (CPC, all-query) | 0.941 | 0.870 | **+0.071** | 0.064 | yes |
115
+ | Cyber (ATT&CK) | 0.766 | 0.749 | +0.017 | 0.073 | directional* |
113
116
 
114
117
  \* Cyber survives Holm across domains but not a conservative
115
118
  Bonferroni-over-baselines selection correction (p=0.035 → 0.17); reported as
@@ -142,6 +145,12 @@ logistic-regression baseline wins. The advantage is specific to structure.
142
145
 
143
146
  ## Quick start
144
147
 
148
+ ```bash
149
+ pip install structuremappingmemory # from PyPI (import name: sma)
150
+ ```
151
+
152
+ Or for development, with the evaluation/encoder extras:
153
+
145
154
  ```bash
146
155
  python -m venv .venv && . .venv/bin/activate
147
156
  pip install -e ".[encoders,eval]"
@@ -4,6 +4,7 @@ pyproject.toml
4
4
  sma/__init__.py
5
5
  sma/__main__.py
6
6
  sma/cli.py
7
+ sma/mcp.py
7
8
  sma/agent/__init__.py
8
9
  sma/agent/adapter_draft.py
9
10
  sma/agent/api.py
@@ -130,5 +131,6 @@ tests/test_bugsinpy_t3.py
130
131
  tests/test_drift.py
131
132
  tests/test_gates.py
132
133
  tests/test_hipporag.py
134
+ tests/test_mcp.py
133
135
  tests/test_production_loop.py
134
136
  tests/test_stats.py
@@ -1,2 +1,3 @@
1
1
  [console_scripts]
2
2
  sma = sma.cli:main
3
+ sma-mcp = sma.mcp:main
@@ -34,3 +34,6 @@ networkx>=3
34
34
  [local-llm]
35
35
  huggingface-hub>=0.23
36
36
  llama-cpp-python>=0.2.90
37
+
38
+ [mcp]
39
+ mcp>=1.0