dandelion-memory 1.0.0rc15__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 (232) hide show
  1. benchmarks/__init__.py +3 -0
  2. benchmarks/adapters/__init__.py +29 -0
  3. benchmarks/adapters/ablations.py +406 -0
  4. benchmarks/adapters/base.py +72 -0
  5. benchmarks/adapters/controlled/__init__.py +13 -0
  6. benchmarks/adapters/controlled/core_archival.py +115 -0
  7. benchmarks/adapters/controlled/extract_resolve.py +130 -0
  8. benchmarks/adapters/controlled/hybrid_rag.py +120 -0
  9. benchmarks/adapters/controlled/no_late_binding.py +113 -0
  10. benchmarks/adapters/dandelion.py +94 -0
  11. benchmarks/adapters/native/__init__.py +31 -0
  12. benchmarks/adapters/native/base.py +45 -0
  13. benchmarks/adapters/native/dandelion_native.py +164 -0
  14. benchmarks/adapters/native/dandelion_native_b0.py +165 -0
  15. benchmarks/adapters/native/dandelion_native_b1.py +170 -0
  16. benchmarks/adapters/native/dandelion_native_b2.py +169 -0
  17. benchmarks/adapters/native/dandelion_native_b3.py +171 -0
  18. benchmarks/adapters/native/dandelion_native_c4.py +261 -0
  19. benchmarks/adapters/native/dandelion_native_c4_1.py +281 -0
  20. benchmarks/adapters/native/dandelion_native_c4_2.py +435 -0
  21. benchmarks/adapters/native/event_evidence_b2.py +616 -0
  22. benchmarks/adapters/native/evidence_b2.py +609 -0
  23. benchmarks/adapters/native/letta_native.py +253 -0
  24. benchmarks/adapters/native/mem0_native.py +219 -0
  25. benchmarks/adapters/native/store_vector_b0.py +792 -0
  26. benchmarks/adapters/native/store_vector_b2.py +808 -0
  27. benchmarks/agent_adapter_stdio_acceptance.py +86 -0
  28. benchmarks/bootstrap_cross_stack.py +218 -0
  29. benchmarks/canonical_profile_instrumentation.py +167 -0
  30. benchmarks/canonical_public_baseline.py +288 -0
  31. benchmarks/cli.py +525 -0
  32. benchmarks/collector_public30_offline.py +1653 -0
  33. benchmarks/datasets/__init__.py +45 -0
  34. benchmarks/datasets/conversational_generator.py +238 -0
  35. benchmarks/datasets/generator.py +283 -0
  36. benchmarks/datasets/holdout_topology_generator.py +290 -0
  37. benchmarks/datasets/holdout_topology_generator_v2.py +294 -0
  38. benchmarks/datasets/multi_world.py +1643 -0
  39. benchmarks/datasets/scale_generator.py +289 -0
  40. benchmarks/datasets/schema.py +57 -0
  41. benchmarks/deep_collision_stress.py +199 -0
  42. benchmarks/evaluators/__init__.py +12 -0
  43. benchmarks/evaluators/manifest.py +142 -0
  44. benchmarks/evaluators/metrics.py +277 -0
  45. benchmarks/evaluators/runner.py +258 -0
  46. benchmarks/f1_step0/build_dev_measurement_manifest_v1.py +127 -0
  47. benchmarks/f1_step0/build_dev_measurement_manifest_v2.py +127 -0
  48. benchmarks/f1_step0/build_dev_measurement_manifest_v3.py +146 -0
  49. benchmarks/f1_step0/build_ledger.py +143 -0
  50. benchmarks/f1_step0/build_ledger_v2.py +296 -0
  51. benchmarks/f1_step0/compare_dev52_readiness_v1.py +106 -0
  52. benchmarks/f1_step0/locator_public_smoke.py +172 -0
  53. benchmarks/f1_step0/prepare_dev52_owner_v1.py +133 -0
  54. benchmarks/f1_step0/prepare_dev52_owner_v2.py +133 -0
  55. benchmarks/f1_step0/public_identity_probe.py +191 -0
  56. benchmarks/f1_step0/public_response_mapper.py +101 -0
  57. benchmarks/f1_step0/real_source_public_preflight.py +244 -0
  58. benchmarks/f1_step0/run_exact_fusion_dev_experiment_v1.py +83 -0
  59. benchmarks/f1_step0/scorer.py +110 -0
  60. benchmarks/f1_step0/source_aware_dev_measurement_v1.py +627 -0
  61. benchmarks/f1_step0/source_aware_dev_measurement_v2.py +659 -0
  62. benchmarks/f1_step0/source_aware_dev_measurement_v3.py +776 -0
  63. benchmarks/f1_step0/test_public_response_mapper.py +110 -0
  64. benchmarks/f1_step0/test_real_source_public_preflight.py +25 -0
  65. benchmarks/f1_step0/test_scorer.py +158 -0
  66. benchmarks/f1_step0/test_source_aware_dev_measurement.py +189 -0
  67. benchmarks/f1_step0/test_source_aware_dev_measurement_v2.py +263 -0
  68. benchmarks/f1_step0/test_source_aware_dev_measurement_v3.py +334 -0
  69. benchmarks/f1_step0/typed_readback_public_smoke.py +181 -0
  70. benchmarks/forensic_c4_1_dev.py +258 -0
  71. benchmarks/forensic_c4_dev.py +255 -0
  72. benchmarks/forensic_deep25_c4_1.py +478 -0
  73. benchmarks/legacy_migration_acceptance.py +29 -0
  74. benchmarks/longmemeval_actor.py +497 -0
  75. benchmarks/longmemeval_agent_judge.py +593 -0
  76. benchmarks/longmemeval_budget.py +139 -0
  77. benchmarks/longmemeval_full.py +410 -0
  78. benchmarks/longmemeval_judge.py +327 -0
  79. benchmarks/longmemeval_pilot.py +316 -0
  80. benchmarks/longmemeval_s.py +318 -0
  81. benchmarks/memory_bench.py +248 -0
  82. benchmarks/memory_formation/__init__.py +1 -0
  83. benchmarks/memory_formation/audit.py +801 -0
  84. benchmarks/memory_formation/real_audit.py +688 -0
  85. benchmarks/memory_session_acceptance.py +279 -0
  86. benchmarks/native_codex_host.py +513 -0
  87. benchmarks/owner_dense_acceptance.py +126 -0
  88. benchmarks/pilot_seeker_vault.py +266 -0
  89. benchmarks/prepare_public30_ripgrep_range_owner.py +115 -0
  90. benchmarks/profile_canonical.py +373 -0
  91. benchmarks/profile_canonical_lanes.py +405 -0
  92. benchmarks/profile_search_chunks.py +351 -0
  93. benchmarks/public_ranges_faq_proof.py +121 -0
  94. benchmarks/public_repos/build_history_scope_v3.py +330 -0
  95. benchmarks/public_repos/build_oracle.py +108 -0
  96. benchmarks/public_repos/build_oracle_v2.py +169 -0
  97. benchmarks/public_repos/build_oracle_v3.py +176 -0
  98. benchmarks/public_repos/map_public_v2.py +296 -0
  99. benchmarks/public_repos/project_prompts_v2.py +31 -0
  100. benchmarks/public_repos/project_prompts_v3.py +29 -0
  101. benchmarks/public_repos/ready_admin_v2.py +214 -0
  102. benchmarks/public_repos/score_public_v2.py +258 -0
  103. benchmarks/public_repos/test_map_public_v2.py +171 -0
  104. benchmarks/public_repos/test_public_repo_v2.py +44 -0
  105. benchmarks/public_repos/test_public_repo_v3.py +46 -0
  106. benchmarks/public_repos/test_ready_admin_v2.py +117 -0
  107. benchmarks/public_repos/test_score_public_v2.py +163 -0
  108. benchmarks/public_repos/test_wire_v2.py +292 -0
  109. benchmarks/public_repos/wire_v2.py +603 -0
  110. benchmarks/real_blocker_fix_acceptance.py +155 -0
  111. benchmarks/real_scan_rescan_acceptance.py +41 -0
  112. benchmarks/recall_v2_20k_smoke.py +112 -0
  113. benchmarks/recall_v2_e1_retrieve.py +70 -0
  114. benchmarks/recall_v2_e1_score.py +271 -0
  115. benchmarks/recall_v2_e2_prepare.py +69 -0
  116. benchmarks/recall_v2_e2_public_score.py +91 -0
  117. benchmarks/recall_v2_e2_score.py +108 -0
  118. benchmarks/recall_v2_navigation_mcp_replay.py +308 -0
  119. benchmarks/recall_v2_reader_capture.py +118 -0
  120. benchmarks/recall_v2_reader_replay.py +136 -0
  121. benchmarks/run_blind_h10_holdout.py +284 -0
  122. benchmarks/run_blind_h9_holdout.py +275 -0
  123. benchmarks/run_c4_2_nightly_scale.py +446 -0
  124. benchmarks/run_context_scale_simulation.py +189 -0
  125. benchmarks/run_conversational_bench.py +170 -0
  126. benchmarks/run_h11_evaluation.py +316 -0
  127. benchmarks/run_h11_v2_evaluation.py +431 -0
  128. benchmarks/run_native_fixed12.py +469 -0
  129. benchmarks/run_native_preflight.py +280 -0
  130. benchmarks/run_scale_stress.py +225 -0
  131. benchmarks/solanazation_brownfield_01/build_oracle.py +970 -0
  132. benchmarks/solanazation_brownfield_01/check_level_a.py +78 -0
  133. benchmarks/solanazation_brownfield_01/debug_scope.py +13 -0
  134. benchmarks/solanazation_brownfield_01/evaluate_bootstrap_02.py +570 -0
  135. benchmarks/solanazation_brownfield_01/evaluate_first_pass.py +306 -0
  136. benchmarks/solanazation_brownfield_01/generate_dryrun_report.py +222 -0
  137. benchmarks/solanazation_brownfield_01/generate_manifests.py +146 -0
  138. benchmarks/solanazation_brownfield_01/inspect_misses.py +29 -0
  139. benchmarks/solanazation_brownfield_01/run_miss_forensic.py +206 -0
  140. benchmarks/solanazation_brownfield_01/test_facts.py +15 -0
  141. benchmarks/statistics/__init__.py +15 -0
  142. benchmarks/statistics/significance.py +326 -0
  143. benchmarks/test_native_mcp_preflight.py +229 -0
  144. benchmarks/test_prepare_public30_range_owner.py +35 -0
  145. benchmarks/test_public30_source_reference.py +698 -0
  146. benchmarks/test_recall_v2_e1_reader_input.py +89 -0
  147. benchmarks/test_recall_v2_navigation_mcp_replay.py +155 -0
  148. benchmarks/test_recall_v2_reader_replay.py +41 -0
  149. dandelion/__init__.py +3 -0
  150. dandelion/__main__.py +11 -0
  151. dandelion/agent_adapter.py +2369 -0
  152. dandelion/bootstrap/__init__.py +872 -0
  153. dandelion/bootstrap/alias_policy.py +50 -0
  154. dandelion/bootstrap/authority.py +225 -0
  155. dandelion/bootstrap/canonical_history.py +412 -0
  156. dandelion/bootstrap/classifier.py +403 -0
  157. dandelion/bootstrap/component_graph.py +280 -0
  158. dandelion/bootstrap/discovery.py +279 -0
  159. dandelion/bootstrap/domain_induction.py +180 -0
  160. dandelion/bootstrap/domain_topology.py +673 -0
  161. dandelion/bootstrap/evidence_envelope.py +132 -0
  162. dandelion/bootstrap/extractors/base.py +171 -0
  163. dandelion/bootstrap/extractors/exact.py +1234 -0
  164. dandelion/bootstrap/extractors/generic.py +100 -0
  165. dandelion/bootstrap/extractors/git_temporal.py +111 -0
  166. dandelion/bootstrap/extractors/registry.py +345 -0
  167. dandelion/bootstrap/extractors/relations.py +89 -0
  168. dandelion/bootstrap/extractors/semantic.py +424 -0
  169. dandelion/bootstrap/extractors/status.py +218 -0
  170. dandelion/bootstrap/git_sweeper.py +135 -0
  171. dandelion/bootstrap/manifest.py +246 -0
  172. dandelion/bootstrap/quarantine.py +264 -0
  173. dandelion/bootstrap/report.py +259 -0
  174. dandelion/bootstrap/root_resolver.py +221 -0
  175. dandelion/bootstrap/tech_profiler.py +125 -0
  176. dandelion/bootstrap/topology.py +75 -0
  177. dandelion/bootstrap/topology_index.py +110 -0
  178. dandelion/bootstrap/transaction.py +315 -0
  179. dandelion/canonical_bootstrap.py +257 -0
  180. dandelion/canonical_memory.py +4130 -0
  181. dandelion/captured_input.py +295 -0
  182. dandelion/cli.py +767 -0
  183. dandelion/config.py +40 -0
  184. dandelion/derived_index.py +745 -0
  185. dandelion/domains.py +706 -0
  186. dandelion/embedder.py +229 -0
  187. dandelion/event_evidence.py +571 -0
  188. dandelion/evidence.py +812 -0
  189. dandelion/evidence_response.py +453 -0
  190. dandelion/evidence_workspace.py +2360 -0
  191. dandelion/exact_context.py +129 -0
  192. dandelion/hydrator.py +124 -0
  193. dandelion/inspector.py +442 -0
  194. dandelion/inspector_assets/dandelion-mark.svg +33 -0
  195. dandelion/inspector_assets/dandelion_graph.js +361 -0
  196. dandelion/inspector_assets/inspector.css +223 -0
  197. dandelion/inspector_assets/inspector.html +135 -0
  198. dandelion/inspector_assets/inspector.js +1059 -0
  199. dandelion/legacy_import.py +431 -0
  200. dandelion/live_source_grants.py +133 -0
  201. dandelion/local_owner.py +114 -0
  202. dandelion/memory_client.py +252 -0
  203. dandelion/memory_contract.py +107 -0
  204. dandelion/memory_owner.py +560 -0
  205. dandelion/reader_providers.py +118 -0
  206. dandelion/recall_v2.py +767 -0
  207. dandelion/reconciler.py +219 -0
  208. dandelion/request_budget.py +125 -0
  209. dandelion/retained_basis.py +223 -0
  210. dandelion/retrieval.py +485 -0
  211. dandelion/router.py +405 -0
  212. dandelion/search_terms.py +53 -0
  213. dandelion/security.py +124 -0
  214. dandelion/server.py +810 -0
  215. dandelion/source_content_fts.py +219 -0
  216. dandelion/source_delivery.py +89 -0
  217. dandelion/source_history.py +241 -0
  218. dandelion/source_identity.py +88 -0
  219. dandelion/source_navigator.py +5 -0
  220. dandelion/source_reader.py +444 -0
  221. dandelion/source_references.py +379 -0
  222. dandelion/source_sections.py +265 -0
  223. dandelion/store_kv.py +684 -0
  224. dandelion/store_vector.py +1289 -0
  225. dandelion/studio.py +325 -0
  226. dandelion/wal.py +227 -0
  227. dandelion_memory-1.0.0rc15.dist-info/METADATA +48 -0
  228. dandelion_memory-1.0.0rc15.dist-info/RECORD +232 -0
  229. dandelion_memory-1.0.0rc15.dist-info/WHEEL +5 -0
  230. dandelion_memory-1.0.0rc15.dist-info/entry_points.txt +5 -0
  231. dandelion_memory-1.0.0rc15.dist-info/licenses/LICENSE +21 -0
  232. dandelion_memory-1.0.0rc15.dist-info/top_level.txt +2 -0
@@ -0,0 +1,130 @@
1
+ """Controlled Architectural Baseline: Extract & Resolve (Mem0 Legacy / Entity-RAG paradigm).
2
+
3
+ Characteristics:
4
+ - Extracts discrete facts into an Entity Store with conflict resolution (ADD / UPDATE).
5
+ - Episodic memory passages are stored as static text strings.
6
+ - When a fact updates, its entity record updates, but past episodic texts remain unchanged.
7
+ - Demonstrates why updating facts alone does not fix historical timeline rewriting or stale state in episodic memory.
8
+ """
9
+
10
+ import re
11
+ from typing import Any, Optional
12
+ from dandelion.store_vector import VectorStore
13
+ from benchmarks.adapters.base import BaseMemoryAdapter
14
+
15
+
16
+ class ExtractResolveAdapter(BaseMemoryAdapter):
17
+ """Extract & Resolve baseline representing entity extraction with fact overwrite."""
18
+
19
+ def __init__(self) -> None:
20
+ self.entity_facts: dict[str, dict[str, Any]] = {}
21
+ self.vec: Optional[VectorStore] = None
22
+ self.reset()
23
+
24
+ @property
25
+ def name(self) -> str:
26
+ return "extract_resolve"
27
+
28
+ def reset(self) -> None:
29
+ self.entity_facts.clear()
30
+ self.vec = VectorStore(in_memory=True)
31
+ import dandelion.store_kv
32
+ dandelion.store_kv._store = None
33
+
34
+ def ingest_fact(
35
+ self,
36
+ key: str,
37
+ value: str,
38
+ timestamp: float,
39
+ domain: str = "general",
40
+ source: str = "config_read",
41
+ ) -> None:
42
+ # Conflict resolution: update entity record with latest observation
43
+ self.entity_facts[key] = {
44
+ "value": value,
45
+ "domain": domain,
46
+ "updated_at": timestamp,
47
+ "source": source,
48
+ }
49
+ # Also index fact representation in vector store
50
+ fact_repr = f"Fact: {key} is {value} in domain {domain}."
51
+ self.vec.save_chunk(
52
+ domain=domain,
53
+ template=fact_repr,
54
+ source=source,
55
+ created_at=timestamp,
56
+ event_id=f"fact_{key}",
57
+ )
58
+
59
+ def ingest_memory(
60
+ self,
61
+ event_id: str,
62
+ template: str,
63
+ domain: str,
64
+ timestamp: float,
65
+ supersedes: Optional[str] = None,
66
+ source: str = "tool_observed",
67
+ ) -> str:
68
+ # Resolve entities eagerly against the current entity store at ingestion time
69
+ def _resolve(m: re.Match) -> str:
70
+ raw_key = m.group(1).split("@rev:")[0]
71
+ if domain:
72
+ qual_key = f"{domain}.{raw_key}"
73
+ if qual_key in self.entity_facts:
74
+ return str(self.entity_facts[qual_key]["value"])
75
+ if raw_key in self.entity_facts:
76
+ return str(self.entity_facts[raw_key]["value"])
77
+ return f"[UNKNOWN: {raw_key}]"
78
+
79
+ inlined = re.sub(r"\{\{(?:ref|snapshot):([\w.:\-]+)\}\}", _resolve, template)
80
+ if domain:
81
+ inlined = inlined.replace("{{domain}}", domain)
82
+
83
+ return self.vec.save_chunk(
84
+ domain=domain,
85
+ template=inlined,
86
+ supersedes=supersedes,
87
+ source=source,
88
+ created_at=timestamp,
89
+ event_id=event_id,
90
+ )
91
+
92
+ def recall(
93
+ self,
94
+ query: str,
95
+ domain: Optional[str] = None,
96
+ limit: int = 3,
97
+ ) -> list[dict[str, Any]]:
98
+ domains = [domain] if domain and not domain.endswith(".*") else None
99
+ raw_results = self.vec.search_chunks(
100
+ query=query,
101
+ domains=domains,
102
+ limit=limit,
103
+ use_reverse_index=False,
104
+ use_lexical_rerank=False,
105
+ )
106
+ return [
107
+ {
108
+ "id": r["id"],
109
+ "event_id": r.get("event_id", ""),
110
+ "text": r["template"],
111
+ "domain": r["domain"],
112
+ "score": r.get("score", 0.0),
113
+ "vector_score": r.get("vector_score", 0.0),
114
+ }
115
+ for r in raw_results
116
+ ]
117
+
118
+ def get_fact(
119
+ self,
120
+ key: str,
121
+ domain: Optional[str] = None,
122
+ at: Optional[float] = None,
123
+ ) -> Optional[str]:
124
+ if domain:
125
+ qual_key = f"{domain}.{key}"
126
+ if qual_key in self.entity_facts:
127
+ return str(self.entity_facts[qual_key]["value"])
128
+ if key in self.entity_facts:
129
+ return str(self.entity_facts[key]["value"])
130
+ return None
@@ -0,0 +1,120 @@
1
+ """Controlled Architectural Baseline: Hybrid RAG (Dense + BM25 Lexical + RRF).
2
+
3
+ Characteristics:
4
+ - Full state-of-the-art hybrid search (Dense FastEmbed ONNX + Lexical tokens via RRF K=60).
5
+ - Eager static text chunks: facts are inlined directly at ingestion time.
6
+ - No symbolic fact store, no late-binding hydration, no reverse fact index.
7
+ - Raw retrieved content returned without server-enforced secret redaction.
8
+ """
9
+
10
+ import re
11
+ from typing import Any, Optional
12
+ from dandelion.store_vector import VectorStore
13
+ from benchmarks.adapters.base import BaseMemoryAdapter
14
+
15
+
16
+ class HybridRAGAdapter(BaseMemoryAdapter):
17
+ """Hybrid RAG baseline combining Dense vector search and BM25-style lexical search."""
18
+
19
+ def __init__(self) -> None:
20
+ self.facts: dict[str, str] = {}
21
+ self.vec: Optional[VectorStore] = None
22
+ self.reset()
23
+
24
+ @property
25
+ def name(self) -> str:
26
+ return "hybrid_rag"
27
+
28
+ def reset(self) -> None:
29
+ self.facts.clear()
30
+ self.vec = VectorStore(in_memory=True)
31
+ import dandelion.store_kv
32
+ dandelion.store_kv._store = None
33
+
34
+ def ingest_fact(
35
+ self,
36
+ key: str,
37
+ value: str,
38
+ timestamp: float,
39
+ domain: str = "general",
40
+ source: str = "config_read",
41
+ ) -> None:
42
+ self.facts[key] = value
43
+ # Index fact as a standalone passage in hybrid store
44
+ fact_sentence = f"Configuration fact '{key}' is set to '{value}' in domain '{domain}'."
45
+ self.vec.save_chunk(
46
+ domain=domain,
47
+ template=fact_sentence,
48
+ source=source,
49
+ created_at=timestamp,
50
+ event_id=f"fact_{key}",
51
+ )
52
+
53
+ def ingest_memory(
54
+ self,
55
+ event_id: str,
56
+ template: str,
57
+ domain: str,
58
+ timestamp: float,
59
+ supersedes: Optional[str] = None,
60
+ source: str = "tool_observed",
61
+ ) -> str:
62
+ def _resolve_eager(m: re.Match) -> str:
63
+ raw_key = m.group(1).split("@rev:")[0]
64
+ if domain:
65
+ qual_key = f"{domain}.{raw_key}"
66
+ if qual_key in self.facts:
67
+ return self.facts[qual_key]
68
+ return self.facts.get(raw_key, f"[MISSING: {raw_key}]")
69
+
70
+ inlined = re.sub(r"\{\{(?:ref|snapshot):([\w.:\-]+)\}\}", _resolve_eager, template)
71
+ if domain:
72
+ inlined = inlined.replace("{{domain}}", domain)
73
+
74
+ return self.vec.save_chunk(
75
+ domain=domain,
76
+ template=inlined,
77
+ supersedes=supersedes,
78
+ source=source,
79
+ created_at=timestamp,
80
+ event_id=event_id,
81
+ )
82
+
83
+ def recall(
84
+ self,
85
+ query: str,
86
+ domain: Optional[str] = None,
87
+ limit: int = 3,
88
+ ) -> list[dict[str, Any]]:
89
+ domains = [domain] if domain and not domain.endswith(".*") else None
90
+ # Full hybrid search (Dense + Lexical via RRF) without Dandelion reverse lookup
91
+ raw_results = self.vec.search_chunks(
92
+ query=query,
93
+ domains=domains,
94
+ limit=limit,
95
+ use_reverse_index=False,
96
+ use_lexical_rerank=True,
97
+ )
98
+ return [
99
+ {
100
+ "id": r["id"],
101
+ "event_id": r.get("event_id", ""),
102
+ "text": r["template"],
103
+ "domain": r["domain"],
104
+ "score": r.get("score", 0.0),
105
+ "vector_score": r.get("vector_score", 0.0),
106
+ }
107
+ for r in raw_results
108
+ ]
109
+
110
+ def get_fact(
111
+ self,
112
+ key: str,
113
+ domain: Optional[str] = None,
114
+ at: Optional[float] = None,
115
+ ) -> Optional[str]:
116
+ if domain:
117
+ qual_key = f"{domain}.{key}"
118
+ if qual_key in self.facts:
119
+ return self.facts[qual_key]
120
+ return self.facts.get(key)
@@ -0,0 +1,113 @@
1
+ """Controlled Architectural Ablation: NoLateBinding (Eager Dandelion).
2
+
3
+ Characteristics:
4
+ - Identical primitives to full Dandelion: KVStore, FastEmbed ONNX, Lexical matching, Reverse Index, RRF.
5
+ - Identical server-side secret filtering policy.
6
+ - ONLY difference: {{ref:key}} and {{snapshot:key}} are resolved and inlined at write-time (ingest_memory)
7
+ rather than hydrated at read-time (recall).
8
+ - Isolates the exact marginal causal lift of Late-Binding Hydration.
9
+ """
10
+
11
+ import re
12
+ from typing import Any, Optional
13
+ from dandelion.store_kv import KVStore
14
+ from dandelion.store_vector import VectorStore
15
+ from benchmarks.adapters.base import BaseMemoryAdapter
16
+
17
+
18
+ class NoLateBindingAdapter(BaseMemoryAdapter):
19
+ """Full Dandelion architecture with late-binding hydration disabled (eager inlining)."""
20
+
21
+ def __init__(self) -> None:
22
+ self.kv: Optional[KVStore] = None
23
+ self.vec: Optional[VectorStore] = None
24
+ self.reset()
25
+
26
+ @property
27
+ def name(self) -> str:
28
+ return "no_late_binding"
29
+
30
+ def reset(self) -> None:
31
+ import dandelion.store_kv
32
+ dandelion.store_kv._store = None
33
+ self.kv = KVStore(path=None)
34
+ dandelion.store_kv._store = self.kv
35
+ self.vec = VectorStore(in_memory=True)
36
+
37
+ def ingest_fact(
38
+ self,
39
+ key: str,
40
+ value: str,
41
+ timestamp: float,
42
+ domain: str = "general",
43
+ source: str = "config_read",
44
+ ) -> None:
45
+ self.kv.set_fact(key, value, source=source, confidence=1.0, timestamp=timestamp)
46
+
47
+ def ingest_memory(
48
+ self,
49
+ event_id: str,
50
+ template: str,
51
+ domain: str,
52
+ timestamp: float,
53
+ supersedes: Optional[str] = None,
54
+ source: str = "tool_observed",
55
+ ) -> str:
56
+ # Eager resolution at ingestion time
57
+ def _resolve_eager(m: re.Match) -> str:
58
+ raw_key = m.group(1).split("@rev:")[0]
59
+ val = None
60
+ if domain:
61
+ val = self.kv.get_fact(f"{domain}.{raw_key}", at_timestamp=timestamp)
62
+ if val is None:
63
+ val = self.kv.get_fact(raw_key, at_timestamp=timestamp)
64
+ return str(val) if val is not None else f"[UNKNOWN: {raw_key}]"
65
+
66
+ inlined = re.sub(r"\{\{(?:ref|snapshot):([\w.:\-]+)\}\}", _resolve_eager, template)
67
+ if domain:
68
+ inlined = inlined.replace("{{domain}}", domain)
69
+
70
+ return self.vec.save_chunk(
71
+ domain=domain,
72
+ template=inlined,
73
+ supersedes=supersedes,
74
+ source=source,
75
+ created_at=timestamp,
76
+ event_id=event_id,
77
+ )
78
+
79
+ def recall(
80
+ self,
81
+ query: str,
82
+ domain: Optional[str] = None,
83
+ limit: int = 3,
84
+ ) -> list[dict[str, Any]]:
85
+ domains = [domain] if domain and not domain.endswith(".*") else None
86
+ raw_results = self.vec.search_chunks(
87
+ query=query,
88
+ domains=domains,
89
+ limit=limit,
90
+ use_reverse_index=True,
91
+ use_lexical_rerank=True,
92
+ )
93
+ return [
94
+ {
95
+ "id": r["id"],
96
+ "event_id": r.get("event_id", ""),
97
+ "text": r["template"], # Raw static text, no runtime hydration
98
+ "domain": r["domain"],
99
+ "score": r.get("score", 0.0),
100
+ "vector_score": r.get("vector_score", 0.0),
101
+ }
102
+ for r in raw_results
103
+ ]
104
+
105
+ def get_fact(
106
+ self,
107
+ key: str,
108
+ domain: Optional[str] = None,
109
+ at: Optional[float] = None,
110
+ ) -> Optional[str]:
111
+ if at is not None:
112
+ return self.kv.get_fact(key, at_timestamp=at)
113
+ return self.kv.resolve_ref(key, domain=domain)
@@ -0,0 +1,94 @@
1
+ """Dandelion adapter for DandelionBench evaluation."""
2
+
3
+ from typing import Any, Optional
4
+ import dandelion.store_kv
5
+ from dandelion.store_kv import KVStore
6
+ from dandelion.store_vector import VectorStore
7
+ from dandelion.hydrator import hydrate_template, pin_snapshots
8
+ from benchmarks.adapters.base import BaseMemoryAdapter
9
+
10
+
11
+ class DandelionAdapter(BaseMemoryAdapter):
12
+ """Full Dandelion adapter with symbolic KV, RRF, snapshots, and reverse index."""
13
+
14
+ def __init__(self) -> None:
15
+ self.kv: Optional[KVStore] = None
16
+ self.vec: Optional[VectorStore] = None
17
+ self.reset()
18
+
19
+ @property
20
+ def name(self) -> str:
21
+ return "dandelion"
22
+
23
+ def reset(self) -> None:
24
+ """Reset internal stores to empty state in memory."""
25
+ self.kv = KVStore(path=None)
26
+ dandelion.store_kv._store = self.kv
27
+ self.vec = VectorStore(in_memory=True)
28
+
29
+ def ingest_fact(
30
+ self,
31
+ key: str,
32
+ value: str,
33
+ timestamp: float,
34
+ domain: str = "general",
35
+ source: str = "config_read",
36
+ ) -> None:
37
+ self.kv.set_fact(key=key, value=value, source=source, timestamp=timestamp)
38
+
39
+ def ingest_memory(
40
+ self,
41
+ event_id: str,
42
+ template: str,
43
+ domain: str,
44
+ timestamp: float,
45
+ supersedes: Optional[str] = None,
46
+ source: str = "tool_observed",
47
+ ) -> str:
48
+ # Pin snapshots at remember-time to lock historical facts
49
+ pinned = pin_snapshots(template, self.kv.get_current_revision, domain)
50
+ return self.vec.save_chunk(
51
+ domain=domain,
52
+ template=pinned,
53
+ supersedes=supersedes,
54
+ source=source,
55
+ created_at=timestamp,
56
+ event_id=event_id,
57
+ )
58
+
59
+ def recall(
60
+ self,
61
+ query: str,
62
+ domain: Optional[str] = None,
63
+ limit: int = 3,
64
+ ) -> list[dict[str, Any]]:
65
+ domains = [domain] if domain and not domain.endswith(".*") else None
66
+ raw_results = self.vec.search_chunks(query=query, domains=domains, limit=limit)
67
+
68
+ out = []
69
+ for r in raw_results:
70
+ hydrated = hydrate_template(
71
+ r["template"],
72
+ kv_resolver=self.kv.resolve_ref,
73
+ chunk_domain=r["domain"],
74
+ allow_secrets=False,
75
+ )
76
+ out.append({
77
+ "id": r["id"],
78
+ "event_id": r.get("event_id", r["id"]),
79
+ "text": hydrated,
80
+ "template": r["template"],
81
+ "domain": r["domain"],
82
+ "score": r["score"],
83
+ })
84
+ return out
85
+
86
+ def get_fact(
87
+ self,
88
+ key: str,
89
+ domain: Optional[str] = None,
90
+ at: Optional[float] = None,
91
+ ) -> Optional[str]:
92
+ if at is not None:
93
+ return self.kv.get_fact(key, at_timestamp=at)
94
+ return self.kv.resolve_ref(key, domain=domain)
@@ -0,0 +1,31 @@
1
+ """Native competitor adapters for DandelionBench Track B.
2
+
3
+ Adapters in this module wrap upstream third-party SDKs (mem0ai, letta-client)
4
+ using pinned library versions, recorded configuration metadata, and dual
5
+ evaluation surfaces (Native Retrieval Surface & Native End-to-End Surface).
6
+ """
7
+
8
+ from benchmarks.adapters.native.base import NativeMemoryAdapter
9
+ from benchmarks.adapters.native.dandelion_native import DandelionNativeAdapter
10
+ from benchmarks.adapters.native.dandelion_native_b0 import DandelionNativeB0Adapter
11
+ from benchmarks.adapters.native.dandelion_native_b1 import DandelionNativeB1Adapter
12
+ from benchmarks.adapters.native.dandelion_native_b2 import DandelionNativeB2Adapter
13
+ from benchmarks.adapters.native.dandelion_native_b3 import DandelionNativeB3Adapter
14
+ from benchmarks.adapters.native.dandelion_native_c4 import DandelionNativeAdapterC4
15
+ from benchmarks.adapters.native.dandelion_native_c4_1 import DandelionNativeAdapterC41
16
+ from benchmarks.adapters.native.mem0_native import Mem0NativeAdapter
17
+ from benchmarks.adapters.native.letta_native import LettaNativeAdapter
18
+
19
+ __all__ = [
20
+ "NativeMemoryAdapter",
21
+ "DandelionNativeAdapter",
22
+ "DandelionNativeB0Adapter",
23
+ "DandelionNativeB1Adapter",
24
+ "DandelionNativeB2Adapter",
25
+ "DandelionNativeB3Adapter",
26
+ "DandelionNativeAdapterC4",
27
+ "DandelionNativeAdapterC41",
28
+ "Mem0NativeAdapter",
29
+ "LettaNativeAdapter",
30
+ ]
31
+
@@ -0,0 +1,45 @@
1
+ """Base interface and telemetry structures for Track B Native Competitor Adapters."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Any, Optional
5
+ from benchmarks.adapters.base import BaseMemoryAdapter
6
+
7
+
8
+ class NativeMemoryAdapter(BaseMemoryAdapter):
9
+ """Abstract interface for Track B native third-party memory systems."""
10
+
11
+ @property
12
+ @abstractmethod
13
+ def package_version(self) -> str:
14
+ """Upstream library package version (e.g. 'mem0ai==2.0.20')."""
15
+ pass
16
+
17
+ @property
18
+ @abstractmethod
19
+ def configuration_metadata(self) -> dict[str, Any]:
20
+ """Hardware, LLM, embedding model, and storage configuration dictionary."""
21
+ pass
22
+
23
+ @property
24
+ @abstractmethod
25
+ def telemetry(self) -> dict[str, Any]:
26
+ """Runtime execution telemetry (tokens, LLM calls, API calls, cost estimate)."""
27
+ pass
28
+
29
+ @abstractmethod
30
+ def answer(
31
+ self,
32
+ query: str,
33
+ domain: Optional[str] = None,
34
+ ) -> dict[str, Any]:
35
+ """End-to-End answering surface.
36
+
37
+ Returns a dictionary containing:
38
+ - 'answer': str (final generated or synthesized answer)
39
+ - 'retrieved_context': str
40
+ - 'context_tokens': int
41
+ - 'llm_calls': int
42
+ - 'api_calls': int
43
+ - 'cost_usd': float
44
+ """
45
+ pass
@@ -0,0 +1,164 @@
1
+ """Dandelion native adapter for DandelionBench Track B."""
2
+
3
+ from typing import Any, Optional
4
+ import dandelion.store_kv
5
+ from dandelion.store_kv import KVStore
6
+ from dandelion.store_vector import VectorStore
7
+ from dandelion.hydrator import hydrate_template, pin_snapshots
8
+ from benchmarks.adapters.native.base import NativeMemoryAdapter
9
+
10
+
11
+ class DandelionNativeAdapter(NativeMemoryAdapter):
12
+ """Full Dandelion native memory engine with Hub & Spoke architecture."""
13
+
14
+ def __init__(self) -> None:
15
+ self.kv: Optional[KVStore] = None
16
+ self.vec: Optional[VectorStore] = None
17
+ self.query_count = 0
18
+ self.total_tokens = 0
19
+ self.reset()
20
+
21
+ @property
22
+ def name(self) -> str:
23
+ return "dandelion_native"
24
+
25
+ @property
26
+ def package_version(self) -> str:
27
+ return "dandelion-0.2.0 (local)"
28
+
29
+ @property
30
+ def configuration_metadata(self) -> dict[str, Any]:
31
+ return {
32
+ "system": "dandelion",
33
+ "architecture": "hub_and_spoke_symbolic_semantic",
34
+ "embedding_model": "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",
35
+ "llm_used": "none (deterministic symbolic hydration)",
36
+ "vector_store": "qdrant_in_memory",
37
+ "hydration_policy": "read_time_late_binding",
38
+ "security_policy": "deny_by_default_secret_masking",
39
+ "reverse_index": "enabled_exact_match",
40
+ }
41
+
42
+ @property
43
+ def telemetry(self) -> dict[str, Any]:
44
+ avg_tok = (self.total_tokens / max(1, self.query_count)) if self.query_count else 0
45
+ return {
46
+ "query_count": self.query_count,
47
+ "llm_calls_total": 0,
48
+ "llm_calls_per_query": 0.0,
49
+ "api_calls_total": 0,
50
+ "api_calls_per_query": 0.0,
51
+ "cost_total_usd": 0.0,
52
+ "cost_per_1k_queries_usd": 0.0,
53
+ "mean_memory_context_tokens": round(avg_tok, 1),
54
+ }
55
+
56
+ def reset(self) -> None:
57
+ self.kv = KVStore(path=None)
58
+ dandelion.store_kv._store = self.kv
59
+ self.vec = VectorStore(in_memory=True)
60
+ self.query_count = 0
61
+ self.total_tokens = 0
62
+
63
+ def ingest_fact(
64
+ self,
65
+ key: str,
66
+ value: str,
67
+ timestamp: float,
68
+ domain: str = "general",
69
+ source: str = "config_read",
70
+ ) -> None:
71
+ self.kv.set_fact(key=key, value=value, source=source, timestamp=timestamp)
72
+
73
+ def ingest_memory(
74
+ self,
75
+ event_id: str,
76
+ template: str,
77
+ domain: str,
78
+ timestamp: float,
79
+ supersedes: Optional[str] = None,
80
+ source: str = "tool_observed",
81
+ ) -> str:
82
+ pinned = pin_snapshots(template, self.kv.get_current_revision, domain)
83
+ return self.vec.save_chunk(
84
+ domain=domain,
85
+ template=pinned,
86
+ supersedes=supersedes,
87
+ source=source,
88
+ created_at=timestamp,
89
+ event_id=event_id,
90
+ )
91
+
92
+ def recall(
93
+ self,
94
+ query: str,
95
+ domain: Optional[str] = None,
96
+ limit: int = 3,
97
+ ) -> list[dict[str, Any]]:
98
+ domains = [domain] if domain else None
99
+ raw_results = self.vec.search_chunks(query=query, domains=domains, limit=limit)
100
+ hydrated: list[dict[str, Any]] = []
101
+
102
+ for r in raw_results:
103
+ text = hydrate_template(
104
+ text=r["template"],
105
+ kv_resolver=self.kv.resolve_ref,
106
+ chunk_domain=r.get("domain", "general"),
107
+ allow_secrets=False,
108
+ )
109
+ hydrated.append({
110
+ "id": r["id"],
111
+ "event_id": r.get("event_id", r["id"]),
112
+ "text": text,
113
+ "domain": r.get("domain", "general"),
114
+ "score": r.get("score", 0.0),
115
+ "vector_score": r.get("vector_score", 0.0),
116
+ "has_fact": r.get("has_fact", False),
117
+ "is_historical": "{{ref:" in r["template"] and "@rev:" in r["template"],
118
+ })
119
+
120
+ self.query_count += 1
121
+ tok_sum = sum(len(h["text"].split()) for h in hydrated)
122
+ self.total_tokens += tok_sum
123
+
124
+ return hydrated
125
+
126
+ def answer(
127
+ self,
128
+ query: str,
129
+ domain: Optional[str] = None,
130
+ ) -> dict[str, Any]:
131
+ """End-to-end answering surface using secure context envelope."""
132
+ candidates = self.recall(query=query, domain=domain, limit=3)
133
+ if not candidates:
134
+ return {
135
+ "answer": "I have no memory regarding this topic.",
136
+ "retrieved_context": "",
137
+ "context_tokens": 0,
138
+ "llm_calls": 0,
139
+ "api_calls": 0,
140
+ "cost_usd": 0.0,
141
+ }
142
+
143
+ context_str = "\n".join(f"[{c['event_id']}] {c['text']}" for c in candidates)
144
+ context_tokens = len(context_str.split())
145
+
146
+ # Construct answering payload
147
+ return {
148
+ "answer": context_str,
149
+ "retrieved_context": context_str,
150
+ "context_tokens": context_tokens,
151
+ "llm_calls": 0,
152
+ "api_calls": 0,
153
+ "cost_usd": 0.0,
154
+ }
155
+
156
+ def get_fact(
157
+ self,
158
+ key: str,
159
+ domain: Optional[str] = None,
160
+ at: Optional[float] = None,
161
+ ) -> Optional[str]:
162
+ if at is not None:
163
+ return self.kv.get_fact(key, at_timestamp=at)
164
+ return self.kv.resolve_ref(key, domain=domain)