membrane-aol 0.2.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 (81) hide show
  1. membrane/__init__.py +209 -0
  2. membrane/adapters/__init__.py +5 -0
  3. membrane/adapters/crewai.py +51 -0
  4. membrane/adapters/haystack.py +51 -0
  5. membrane/adapters/langchain.py +113 -0
  6. membrane/adapters/llamaindex.py +90 -0
  7. membrane/adapters/mcp_resources.py +35 -0
  8. membrane/adapters/mcp_server.py +451 -0
  9. membrane/async_preflight.py +118 -0
  10. membrane/cache/__init__.py +5 -0
  11. membrane/cache/embedding_cache.py +198 -0
  12. membrane/cache_format.py +104 -0
  13. membrane/chunking/__init__.py +21 -0
  14. membrane/chunking/boundaries.py +90 -0
  15. membrane/chunking/engine.py +54 -0
  16. membrane/chunking/strategies.py +229 -0
  17. membrane/classifier/__init__.py +5 -0
  18. membrane/classifier/classifier.py +124 -0
  19. membrane/classifier/features.py +51 -0
  20. membrane/classifier/weights.json +4 -0
  21. membrane/cli/__init__.py +1 -0
  22. membrane/cli/__main__.py +6 -0
  23. membrane/cli/diagnostics.py +121 -0
  24. membrane/cli/inspect.py +63 -0
  25. membrane/cli/main.py +112 -0
  26. membrane/cli/profile_cmd.py +59 -0
  27. membrane/cli/quote.py +72 -0
  28. membrane/compile_wiring.py +136 -0
  29. membrane/compiler.py +1987 -0
  30. membrane/config.py +166 -0
  31. membrane/conversation/__init__.py +5 -0
  32. membrane/conversation/manager.py +119 -0
  33. membrane/conversation/summarizer.py +50 -0
  34. membrane/conversation/window.py +18 -0
  35. membrane/dedup/__init__.py +5 -0
  36. membrane/dedup/deduplicator.py +172 -0
  37. membrane/environment.py +100 -0
  38. membrane/exceptions.py +28 -0
  39. membrane/firewall.py +241 -0
  40. membrane/freshness/__init__.py +16 -0
  41. membrane/freshness/decay.py +37 -0
  42. membrane/freshness/manager.py +115 -0
  43. membrane/glaciator.py +90 -0
  44. membrane/interrupt.py +46 -0
  45. membrane/layout.py +107 -0
  46. membrane/llm_adapters.py +55 -0
  47. membrane/llm_capabilities.json +38 -0
  48. membrane/lod.py +132 -0
  49. membrane/memory_protocol.py +267 -0
  50. membrane/metrics.py +128 -0
  51. membrane/observability/__init__.py +16 -0
  52. membrane/observability/callbacks.py +35 -0
  53. membrane/observability/collectors.py +60 -0
  54. membrane/observability/events.py +24 -0
  55. membrane/observability/profiler.py +61 -0
  56. membrane/pair_baselines.py +181 -0
  57. membrane/policies/__init__.py +5 -0
  58. membrane/policies/engine.py +222 -0
  59. membrane/profile.py +142 -0
  60. membrane/provider_registry.py +178 -0
  61. membrane/ranking_diagnostics.py +133 -0
  62. membrane/ref_map.py +124 -0
  63. membrane/runtime/__init__.py +10 -0
  64. membrane/runtime/signals.py +66 -0
  65. membrane/schemas/__init__.py +3 -0
  66. membrane/schemas/kernel_memory_object.schema.json +43 -0
  67. membrane/serializers.py +246 -0
  68. membrane/stress.py +72 -0
  69. membrane/templates/__init__.py +5 -0
  70. membrane/templates/engine.py +186 -0
  71. membrane/templates/parser.py +209 -0
  72. membrane/templates/types.py +60 -0
  73. membrane/tokenizers/__init__.py +18 -0
  74. membrane/tokenizers/adapters.py +98 -0
  75. membrane/tokenizers/registry.py +92 -0
  76. membrane_aol-0.2.0.dist-info/METADATA +846 -0
  77. membrane_aol-0.2.0.dist-info/RECORD +81 -0
  78. membrane_aol-0.2.0.dist-info/WHEEL +4 -0
  79. membrane_aol-0.2.0.dist-info/entry_points.txt +2 -0
  80. membrane_aol-0.2.0.dist-info/licenses/LICENSE +202 -0
  81. membrane_aol-0.2.0.dist-info/licenses/NOTICE +4 -0
membrane/__init__.py ADDED
@@ -0,0 +1,209 @@
1
+ """Membrane — prompt budgeting, U-shaped layout, caching, and retrieval hooks."""
2
+
3
+ from membrane.async_preflight import (
4
+ get_preflight_cache,
5
+ get_preflight_cache_key_prefix,
6
+ get_preflight_cache_ttl_default,
7
+ run_async_preflight,
8
+ set_preflight_cache,
9
+ )
10
+ from membrane.cache_format import (
11
+ CacheableBlock,
12
+ anthropic_system_blocks,
13
+ assemble_system_content,
14
+ build_system_messages,
15
+ is_anthropic_model,
16
+ make_cacheable_block,
17
+ )
18
+ from membrane.compiler import (
19
+ PREFLIGHT_ANALYST_SYSTEM,
20
+ INTERRUPT_INSTRUCTION,
21
+ MEMORY_PLACEMENT_END,
22
+ MEMORY_PLACEMENT_SYSTEM,
23
+ CompileQuote,
24
+ CompileResult,
25
+ ContextCompiler,
26
+ SecurityFlag,
27
+ compile_anonymous,
28
+ compile_prompt_async,
29
+ compile_prompt_payload,
30
+ compile_quote_from_result,
31
+ )
32
+ from membrane.environment import (
33
+ EnvironmentSnapshot,
34
+ capture_cpu_load,
35
+ clear_environment_snapshot,
36
+ get_current_environment_state,
37
+ set_environment_snapshot,
38
+ set_environment_snapshot_from_state,
39
+ snapshot_to_stress_inputs,
40
+ )
41
+ from membrane.firewall import (
42
+ TIER_EPISODIC,
43
+ TIER_PROCEDURAL,
44
+ TIER_SEMANTIC,
45
+ PreFlightContextFirewall,
46
+ count_tokens,
47
+ )
48
+ from membrane.glaciator import GlaciateResult, glaciate
49
+ from membrane.interrupt import (
50
+ INTERRUPT_TAG_END,
51
+ INTERRUPT_TAG_START,
52
+ contains_interrupt,
53
+ parse_interrupt,
54
+ strip_all_interrupts,
55
+ )
56
+ from membrane.layout import ContextLayoutEngine
57
+ from membrane.exceptions import (
58
+ FeatureNotAvailable,
59
+ MembraneError,
60
+ MemoryCollisionError,
61
+ )
62
+ from membrane.llm_adapters import (
63
+ CallablePreFlightAnalyst,
64
+ PreFlightAnalyst,
65
+ as_preflight_analyst,
66
+ )
67
+ from membrane.lod import (
68
+ compute_attention_shift,
69
+ compute_attention_shift_with_embeddings,
70
+ cosine_distance,
71
+ embed_texts,
72
+ )
73
+ from membrane.metrics import (
74
+ CompileMetrics,
75
+ LoggingMetricsCallback,
76
+ MetricsCallback,
77
+ NoOpMetricsCallback,
78
+ build_compile_metrics,
79
+ )
80
+ from membrane.memory_protocol import (
81
+ EpisodicMemory,
82
+ KERNEL_MEMORY_SCHEMA_ID,
83
+ KERNEL_MEMORY_SCHEMA_VERSION,
84
+ KernelMemoryObject,
85
+ KernelMemoryRecord,
86
+ MemoryItem,
87
+ ProceduralMemory,
88
+ ProceduralStep,
89
+ SemanticMemory,
90
+ infer_memory_kind,
91
+ kernel_memory_to_memory_item,
92
+ load_kernel_memory_json_schema,
93
+ memory_item_to_kernel_memory,
94
+ normalize_compile_memories,
95
+ )
96
+ from membrane.provider_registry import (
97
+ get_capabilities_for_model,
98
+ get_context_cap_for_model,
99
+ lookup_context_cap,
100
+ get_full_capability_map,
101
+ get_full_context_caps,
102
+ has_prompt_caching,
103
+ has_streaming_interrupt,
104
+ reload_capability_maps,
105
+ )
106
+ from membrane.ranking_diagnostics import RankingWeights, compute_ranking_diagnostics
107
+ from membrane.ref_map import get_ref_map_for_conversation, set_ref_map_for_conversation
108
+ from membrane.serializers import DenseSerializer
109
+ from membrane.stress import (
110
+ compute_stress_from_environment,
111
+ get_recent_stress_for_user,
112
+ set_recent_stress_for_user,
113
+ )
114
+
115
+ PreFlightFirewall = PreFlightContextFirewall
116
+ LayoutEngine = ContextLayoutEngine
117
+
118
+ __all__ = [
119
+ "INTERRUPT_INSTRUCTION",
120
+ "INTERRUPT_TAG_END",
121
+ "INTERRUPT_TAG_START",
122
+ "KERNEL_MEMORY_SCHEMA_ID",
123
+ "KERNEL_MEMORY_SCHEMA_VERSION",
124
+ "MEMORY_PLACEMENT_END",
125
+ "MEMORY_PLACEMENT_SYSTEM",
126
+ "PREFLIGHT_ANALYST_SYSTEM",
127
+ "TIER_EPISODIC",
128
+ "TIER_PROCEDURAL",
129
+ "TIER_SEMANTIC",
130
+ "CacheableBlock",
131
+ "CallablePreFlightAnalyst",
132
+ "CompileMetrics",
133
+ "CompileQuote",
134
+ "CompileResult",
135
+ "ContextCompiler",
136
+ "ContextLayoutEngine",
137
+ "DenseSerializer",
138
+ "EnvironmentSnapshot",
139
+ "EpisodicMemory",
140
+ "FeatureNotAvailable",
141
+ "GlaciateResult",
142
+ "KernelMemoryObject",
143
+ "KernelMemoryRecord",
144
+ "LayoutEngine",
145
+ "LoggingMetricsCallback",
146
+ "MembraneError",
147
+ "MemoryCollisionError",
148
+ "MemoryItem",
149
+ "MetricsCallback",
150
+ "NoOpMetricsCallback",
151
+ "PreFlightAnalyst",
152
+ "PreFlightContextFirewall",
153
+ "PreFlightFirewall",
154
+ "ProceduralMemory",
155
+ "ProceduralStep",
156
+ "RankingWeights",
157
+ "SecurityFlag",
158
+ "SemanticMemory",
159
+ "anthropic_system_blocks",
160
+ "as_preflight_analyst",
161
+ "assemble_system_content",
162
+ "build_compile_metrics",
163
+ "build_system_messages",
164
+ "capture_cpu_load",
165
+ "clear_environment_snapshot",
166
+ "compile_anonymous",
167
+ "compile_prompt_async",
168
+ "compile_prompt_payload",
169
+ "compile_quote_from_result",
170
+ "compute_attention_shift",
171
+ "compute_attention_shift_with_embeddings",
172
+ "compute_ranking_diagnostics",
173
+ "compute_stress_from_environment",
174
+ "contains_interrupt",
175
+ "cosine_distance",
176
+ "count_tokens",
177
+ "embed_texts",
178
+ "get_capabilities_for_model",
179
+ "get_context_cap_for_model",
180
+ "get_current_environment_state",
181
+ "get_full_capability_map",
182
+ "get_full_context_caps",
183
+ "get_preflight_cache",
184
+ "get_preflight_cache_key_prefix",
185
+ "get_preflight_cache_ttl_default",
186
+ "get_recent_stress_for_user",
187
+ "get_ref_map_for_conversation",
188
+ "glaciate",
189
+ "has_prompt_caching",
190
+ "has_streaming_interrupt",
191
+ "infer_memory_kind",
192
+ "is_anthropic_model",
193
+ "kernel_memory_to_memory_item",
194
+ "load_kernel_memory_json_schema",
195
+ "lookup_context_cap",
196
+ "make_cacheable_block",
197
+ "memory_item_to_kernel_memory",
198
+ "normalize_compile_memories",
199
+ "parse_interrupt",
200
+ "reload_capability_maps",
201
+ "run_async_preflight",
202
+ "set_environment_snapshot",
203
+ "set_environment_snapshot_from_state",
204
+ "set_preflight_cache",
205
+ "set_recent_stress_for_user",
206
+ "set_ref_map_for_conversation",
207
+ "snapshot_to_stress_inputs",
208
+ "strip_all_interrupts",
209
+ ]
@@ -0,0 +1,5 @@
1
+ """Framework adapters (optional extras)."""
2
+
3
+ from membrane.exceptions import MemoryCollisionError
4
+
5
+ __all__ = ["MemoryCollisionError"]
@@ -0,0 +1,51 @@
1
+ """CrewAI: agent-level context hook pattern."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Literal, cast
6
+ from collections.abc import Sequence
7
+
8
+ from membrane.memory_protocol import MemoryItem
9
+
10
+ MemoryType = Literal["semantic", "episodic", "procedural"]
11
+
12
+
13
+ def _memory_type(value: Any) -> MemoryType:
14
+ text = str(value)
15
+ if text in ("semantic", "episodic", "procedural"):
16
+ return cast(MemoryType, text)
17
+ return "semantic"
18
+
19
+
20
+ def document_to_memory(doc: Any) -> MemoryItem:
21
+ """Map a generic CrewAI / dict-like document to
22
+ :class:`~membrane.memory_protocol.MemoryItem`.
23
+ """
24
+ if isinstance(doc, dict):
25
+ return MemoryItem(
26
+ id=str(doc.get("id", "doc")),
27
+ content=str(doc.get("content", "")),
28
+ memory_type=_memory_type(doc.get("memory_type", "semantic")),
29
+ priority=int(doc.get("priority", 2)),
30
+ )
31
+ content = getattr(doc, "content", None) or getattr(doc, "raw", str(doc))
32
+ return MemoryItem(
33
+ id=str(getattr(doc, "id", "doc")),
34
+ content=str(content),
35
+ memory_type="semantic",
36
+ priority=2,
37
+ )
38
+
39
+
40
+ def compile_for_agent(
41
+ compiler: Any,
42
+ *,
43
+ memories: Sequence[Any],
44
+ user_message: str,
45
+ profile: Any = None,
46
+ ) -> Any:
47
+ """Run :meth:`~membrane.compiler.ContextCompiler.compile` for one agent turn."""
48
+ fn = getattr(compiler, "compile", None)
49
+ if fn is None:
50
+ raise TypeError("compiler must have compile()")
51
+ return fn(memories=memories, user_message=user_message, profile=profile)
@@ -0,0 +1,51 @@
1
+ """Haystack: document conversion and prompt assembly hook."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Literal, cast
6
+ from collections.abc import Sequence
7
+
8
+ from membrane.memory_protocol import MemoryItem
9
+
10
+ MemoryType = Literal["semantic", "episodic", "procedural"]
11
+
12
+
13
+ def _memory_type(value: Any) -> MemoryType:
14
+ text = str(value)
15
+ if text in ("semantic", "episodic", "procedural"):
16
+ return cast(MemoryType, text)
17
+ return "semantic"
18
+
19
+
20
+ def haystack_document_to_memory(doc: Any) -> MemoryItem:
21
+ """Convert Haystack ``Document`` to :class:`~membrane.memory_protocol.MemoryItem`."""
22
+ try:
23
+ from haystack import Document # type: ignore
24
+ except ImportError as e:
25
+ raise ImportError("membrane-aol[haystack] requires haystack-ai") from e
26
+ if not isinstance(doc, Document):
27
+ raise TypeError("expected haystack.Document")
28
+ meta = doc.meta or {}
29
+ return MemoryItem(
30
+ id=str(meta.get("id", doc.id or "doc")),
31
+ content=doc.content or "",
32
+ memory_type=_memory_type(meta.get("memory_type", "semantic")),
33
+ priority=int(meta.get("priority", 2)),
34
+ )
35
+
36
+
37
+ class MembranePromptBuilder:
38
+ """Callable that returns Membrane-compiled system text for Haystack pipelines."""
39
+
40
+ def __init__(self, compiler: Any, memories: Sequence[Any], *, profile: Any = None) -> None:
41
+ self._compiler = compiler
42
+ self._memories = memories
43
+ self._profile = profile
44
+
45
+ def __call__(self, query: str) -> str:
46
+ fn = getattr(self._compiler, "compile", None)
47
+ if fn is None:
48
+ raise TypeError("compiler must have compile()")
49
+ res = fn(memories=self._memories, user_message=query, profile=self._profile)
50
+ msgs = getattr(res, "messages", []) or []
51
+ return "\n".join(str(m.get("content", "")) for m in msgs if isinstance(m, dict))
@@ -0,0 +1,113 @@
1
+ """
2
+ LangChain integration.
3
+
4
+ **Collision prevention:** Membrane replaces LangChain's conversation memory when you use
5
+ :class:`MembranePromptTemplate`. Do not combine with ``ConversationBufferMemory`` /
6
+ ``ConversationSummaryMemory`` on the same chain — call :func:`disable_langchain_memory` on the
7
+ chain or raise :class:`membrane.exceptions.MemoryCollisionError` if both are detected.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import contextlib
13
+
14
+ from typing import Any, Literal, cast
15
+ from collections.abc import Sequence
16
+
17
+ from membrane.exceptions import MemoryCollisionError
18
+ from membrane.memory_protocol import MemoryItem
19
+
20
+ MemoryType = Literal["semantic", "episodic", "procedural"]
21
+
22
+
23
+ def _memory_type(value: Any) -> MemoryType:
24
+ text = str(value)
25
+ if text in ("semantic", "episodic", "procedural"):
26
+ return cast(MemoryType, text)
27
+ return "semantic"
28
+
29
+
30
+ def _priority(value: Any) -> int:
31
+ try:
32
+ return max(1, min(3, int(value)))
33
+ except (TypeError, ValueError):
34
+ return 2
35
+
36
+
37
+ def langchain_doc_to_memory(doc: Any) -> MemoryItem:
38
+ """Convert a LangChain ``Document`` to :class:`~membrane.memory_protocol.MemoryItem`."""
39
+ try:
40
+ from langchain_core.documents import Document # type: ignore
41
+ except ImportError as e:
42
+ raise ImportError("membrane-aol[langchain] requires langchain-core") from e
43
+ if not isinstance(doc, Document):
44
+ raise TypeError("expected langchain_core.documents.Document")
45
+ return MemoryItem(
46
+ id=str(doc.metadata.get("id", doc.metadata.get("source", "doc"))),
47
+ content=doc.page_content,
48
+ memory_type=_memory_type(doc.metadata.get("memory_type", "semantic")),
49
+ priority=_priority(doc.metadata.get("priority", 2)),
50
+ )
51
+
52
+
53
+ class MembranePromptTemplate:
54
+ """
55
+ Wraps formatting so ``ContextCompiler.compile`` runs before prompt construction.
56
+
57
+ Pass ``compiler`` (:class:`~membrane.compiler.ContextCompiler`), ``memories``, and
58
+ ``profile`` / kwargs supported by :meth:`~membrane.compiler.ContextCompiler.compile`.
59
+ """
60
+
61
+ def __init__(
62
+ self,
63
+ compiler: Any,
64
+ template: str,
65
+ *,
66
+ memories: Sequence[Any],
67
+ profile: Any = None,
68
+ **compile_kwargs: Any,
69
+ ) -> None:
70
+ self._compiler = compiler
71
+ self._template = template
72
+ self._memories = memories
73
+ self._profile = profile
74
+ self._compile_kwargs = compile_kwargs
75
+
76
+ def format(self, **kwargs: Any) -> str:
77
+ user_message = kwargs.get("user_message") or kwargs.get("input") or ""
78
+ if not user_message:
79
+ raise ValueError("MembranePromptTemplate.format requires user_message or input")
80
+ compile_fn = getattr(self._compiler, "compile", None)
81
+ if compile_fn is None:
82
+ raise TypeError("compiler must have compile()")
83
+ res = compile_fn(
84
+ memories=self._memories,
85
+ user_message=str(user_message),
86
+ profile=self._profile,
87
+ **self._compile_kwargs,
88
+ )
89
+ msgs = getattr(res, "messages", []) or []
90
+ core = "\n".join(str(m.get("content", "")) for m in msgs if isinstance(m, dict))
91
+ fmt_kwargs = dict(kwargs)
92
+ fmt_kwargs["compiled_context"] = core
93
+ return self._template.format(**fmt_kwargs)
94
+
95
+
96
+ def disable_langchain_memory(chain: Any) -> Any:
97
+ """Remove common LangChain memory components from a chain (best-effort)."""
98
+ mem_attrs = ("memory", "memory_key")
99
+ for a in mem_attrs:
100
+ if hasattr(chain, a):
101
+ with contextlib.suppress(Exception):
102
+ setattr(chain, a, None)
103
+ return chain
104
+
105
+
106
+ def detect_memory_collision(chain: Any, membrane_active: bool) -> None:
107
+ if not membrane_active:
108
+ return
109
+ if hasattr(chain, "memory") and getattr(chain, "memory", None) is not None:
110
+ raise MemoryCollisionError(
111
+ "LangChain memory is set while Membrane manages context. "
112
+ "Remove ConversationBufferMemory or disable Membrane."
113
+ )
@@ -0,0 +1,90 @@
1
+ """LlamaIndex: convert retrieved nodes to Membrane memories and post-process."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import contextlib
6
+ from typing import Any
7
+
8
+ from membrane.memory_protocol import KernelMemoryObject
9
+
10
+
11
+ def node_to_memory(node: Any) -> KernelMemoryObject:
12
+ """Convert a LlamaIndex ``NodeWithScore`` (or similar) to
13
+ :class:`~membrane.memory_protocol.KernelMemoryObject`.
14
+
15
+ The retrieval score becomes ``relevance_score``; the firewall tier is always 2 (semantic).
16
+ """
17
+ text = getattr(node, "text", None) or getattr(node, "get_content", lambda: "")()
18
+ if callable(text):
19
+ text = text()
20
+ nid = getattr(node, "id_", None) or getattr(node, "node_id", None) or "node"
21
+ score = getattr(node, "score", None)
22
+ if score is None and hasattr(node, "metadata"):
23
+ score = node.metadata.get("score")
24
+ relevance = 1.0
25
+ if score is not None:
26
+ with contextlib.suppress(TypeError, ValueError):
27
+ relevance = max(0.0, float(score))
28
+ return KernelMemoryObject(
29
+ id=str(nid),
30
+ payload=str(text),
31
+ relevance_score=relevance,
32
+ semantic_tier=2,
33
+ )
34
+
35
+
36
+ try:
37
+ from llama_index.core.bridge.pydantic import PrivateAttr # type: ignore
38
+ from llama_index.core.postprocessor.types import BaseNodePostprocessor # type: ignore
39
+ from llama_index.core.schema import NodeWithScore, TextNode # type: ignore
40
+
41
+ _HAS_LLAMA_INDEX = True
42
+ except ImportError: # pragma: no cover - exercised only without the extra
43
+ _HAS_LLAMA_INDEX = False
44
+ BaseNodePostprocessor = object # type: ignore[assignment,misc]
45
+
46
+ def PrivateAttr(*args: Any, **kwargs: Any) -> Any: # type: ignore[misc]
47
+ return None
48
+
49
+
50
+ class MembraneNodePostprocessor(BaseNodePostprocessor): # type: ignore[misc,valid-type]
51
+ """
52
+ Postprocessor that runs Membrane's compiler on retrieved nodes.
53
+
54
+ Requires ``llama-index-core`` and a configured :class:`~membrane.compiler.ContextCompiler`.
55
+ """
56
+
57
+ _compiler: Any = PrivateAttr()
58
+ _max_tokens: int = PrivateAttr()
59
+ _profile: Any = PrivateAttr()
60
+
61
+ def __init__(self, compiler: Any, *, max_tokens: int = 8192, profile: Any = None) -> None:
62
+ if not _HAS_LLAMA_INDEX:
63
+ raise ImportError("membrane-aol[llamaindex] requires llama-index-core")
64
+ super().__init__()
65
+ self._compiler = compiler
66
+ self._max_tokens = max_tokens
67
+ self._profile = profile
68
+
69
+ @classmethod
70
+ def class_name(cls) -> str:
71
+ return "MembraneNodePostprocessor"
72
+
73
+ def _postprocess_nodes(self, nodes: list[Any], query_bundle: Any = None) -> list[Any]:
74
+ memories = [node_to_memory(n) for n in nodes]
75
+ q = ""
76
+ if query_bundle is not None:
77
+ q = str(getattr(query_bundle, "query_str", None) or getattr(query_bundle, "query", ""))
78
+ compile_fn = getattr(self._compiler, "compile", None)
79
+ if compile_fn is None:
80
+ return nodes
81
+ res = compile_fn(
82
+ memories=memories,
83
+ user_message=q or "context",
84
+ profile=self._profile,
85
+ )
86
+ # Return a single synthetic node carrying compiled text (caller interprets)
87
+ text = "\n".join(
88
+ str(m.get("content", "")) for m in getattr(res, "messages", []) or []
89
+ )
90
+ return [NodeWithScore(node=TextNode(text=text, metadata={"membrane": True}))]
@@ -0,0 +1,35 @@
1
+ """MCP resource payloads for profiles, last compile, and metrics (session state)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import Any
7
+
8
+ from membrane.profile import PRESET_NAMES
9
+
10
+
11
+ _PROFILE_NAMES = PRESET_NAMES
12
+
13
+
14
+ def list_profile_resource_text() -> str:
15
+ return json.dumps({"presets": list(_PROFILE_NAMES)}, indent=2)
16
+
17
+
18
+ def last_compile_resource_text(last: Any | None) -> str:
19
+ if last is None:
20
+ return json.dumps({"error": "no_compilation_yet"})
21
+ if hasattr(last, "to_debug_dict"):
22
+ return json.dumps(last.to_debug_dict(), default=str, indent=2)
23
+ return json.dumps({"raw": str(last)})
24
+
25
+
26
+ def metrics_resource_text(snapshot: dict[str, Any] | None) -> str:
27
+ return json.dumps(snapshot or {"session": "membrane-mcp", "metrics": "none"}, indent=2)
28
+
29
+
30
+ def resource_uris() -> list[str]:
31
+ return [
32
+ "membrane://profiles",
33
+ "membrane://last_compile",
34
+ "membrane://metrics",
35
+ ]