autogen-substrate-memory 0.1.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.
@@ -0,0 +1,6 @@
1
+ """SUBSTRATE cognitive memory provider for Microsoft AutoGen agents."""
2
+
3
+ from autogen_substrate.memory import SubstrateMemory
4
+
5
+ __all__ = ["SubstrateMemory"]
6
+ __version__ = "0.1.0"
@@ -0,0 +1,173 @@
1
+ """Lightweight async HTTP client for the SUBSTRATE MCP server.
2
+
3
+ Sends JSON-RPC 2.0 requests over HTTPS with Bearer token authentication.
4
+ All responses are validated and errors are surfaced as typed exceptions.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import logging
11
+ from dataclasses import dataclass, field
12
+ from typing import Any
13
+
14
+ import httpx
15
+
16
+ logger = logging.getLogger("autogen_substrate.client")
17
+
18
+ DEFAULT_MCP_URL = "https://substrate.garmolabs.com/mcp-server/mcp"
19
+ DEFAULT_TIMEOUT_SECONDS = 30.0
20
+
21
+
22
+ class SubstrateClientError(Exception):
23
+ """Base exception for SUBSTRATE client errors."""
24
+
25
+
26
+ class SubstrateAuthError(SubstrateClientError):
27
+ """Raised when the API key is invalid or missing."""
28
+
29
+
30
+ class SubstrateRPCError(SubstrateClientError):
31
+ """Raised when the MCP server returns a JSON-RPC error."""
32
+
33
+ def __init__(self, code: int, message: str, data: Any = None) -> None:
34
+ self.code = code
35
+ self.rpc_message = message
36
+ self.data = data
37
+ super().__init__(f"JSON-RPC error {code}: {message}")
38
+
39
+
40
+ @dataclass(frozen=True)
41
+ class RPCResponse:
42
+ """Immutable wrapper around a successful JSON-RPC result."""
43
+
44
+ id: int | str | None
45
+ result: Any
46
+
47
+
48
+ @dataclass(frozen=True)
49
+ class SubstrateClientConfig:
50
+ """Immutable configuration for the SUBSTRATE MCP client."""
51
+
52
+ api_key: str
53
+ mcp_url: str = DEFAULT_MCP_URL
54
+ timeout_seconds: float = DEFAULT_TIMEOUT_SECONDS
55
+ extra_headers: dict[str, str] = field(default_factory=dict)
56
+
57
+
58
+ class SubstrateClient:
59
+ """Async HTTP client for SUBSTRATE MCP JSON-RPC calls.
60
+
61
+ Usage::
62
+
63
+ config = SubstrateClientConfig(api_key="sk_sub_...")
64
+ async with SubstrateClient(config) as client:
65
+ result = await client.call_tool("memory_search", {"query": "hello"})
66
+ """
67
+
68
+ def __init__(self, config: SubstrateClientConfig) -> None:
69
+ if not config.api_key:
70
+ raise SubstrateAuthError("SUBSTRATE API key must not be empty")
71
+ self._config = config
72
+ self._request_id = 0
73
+ self._http: httpx.AsyncClient | None = None
74
+
75
+ async def __aenter__(self) -> SubstrateClient:
76
+ self._http = httpx.AsyncClient(
77
+ base_url=self._config.mcp_url,
78
+ headers={
79
+ "Authorization": f"Bearer {self._config.api_key}",
80
+ "Content-Type": "application/json",
81
+ **self._config.extra_headers,
82
+ },
83
+ timeout=httpx.Timeout(self._config.timeout_seconds),
84
+ )
85
+ return self
86
+
87
+ async def __aexit__(self, *exc: object) -> None:
88
+ if self._http is not None:
89
+ await self._http.aclose()
90
+ self._http = None
91
+
92
+ def _next_id(self) -> int:
93
+ self._request_id += 1
94
+ return self._request_id
95
+
96
+ async def _send_rpc(self, method: str, params: dict[str, Any] | None = None) -> RPCResponse:
97
+ """Send a JSON-RPC 2.0 request and return the parsed response."""
98
+ if self._http is None:
99
+ raise SubstrateClientError("Client not initialized. Use 'async with' context manager.")
100
+
101
+ request_id = self._next_id()
102
+ payload: dict[str, Any] = {
103
+ "jsonrpc": "2.0",
104
+ "id": request_id,
105
+ "method": method,
106
+ }
107
+ if params is not None:
108
+ payload["params"] = params
109
+
110
+ logger.debug("MCP request: method=%s id=%d", method, request_id)
111
+
112
+ try:
113
+ response = await self._http.post("", content=json.dumps(payload))
114
+ except httpx.TimeoutException as exc:
115
+ raise SubstrateClientError(f"Request timed out after {self._config.timeout_seconds}s") from exc
116
+ except httpx.HTTPError as exc:
117
+ raise SubstrateClientError(f"HTTP error: {exc}") from exc
118
+
119
+ if response.status_code == 401:
120
+ raise SubstrateAuthError("Invalid or expired API key")
121
+ if response.status_code == 429:
122
+ raise SubstrateClientError("Rate limit exceeded")
123
+ if response.status_code >= 400:
124
+ raise SubstrateClientError(
125
+ f"HTTP {response.status_code}: {response.text[:200]}"
126
+ )
127
+
128
+ try:
129
+ body = response.json()
130
+ except (json.JSONDecodeError, ValueError) as exc:
131
+ raise SubstrateClientError(f"Invalid JSON response: {response.text[:200]}") from exc
132
+
133
+ if "error" in body and body["error"] is not None:
134
+ err = body["error"]
135
+ raise SubstrateRPCError(
136
+ code=err.get("code", -32603),
137
+ message=err.get("message", "Unknown error"),
138
+ data=err.get("data"),
139
+ )
140
+
141
+ return RPCResponse(id=body.get("id"), result=body.get("result"))
142
+
143
+ async def call_tool(self, tool_name: str, arguments: dict[str, Any] | None = None) -> Any:
144
+ """Call an MCP tool and return the result content.
145
+
146
+ Args:
147
+ tool_name: Name of the SUBSTRATE MCP tool (e.g. ``hybrid_search``).
148
+ arguments: Tool-specific arguments dict.
149
+
150
+ Returns:
151
+ The ``result`` field from the JSON-RPC response.
152
+
153
+ Raises:
154
+ SubstrateRPCError: If the server returns a JSON-RPC error.
155
+ SubstrateAuthError: If authentication fails.
156
+ SubstrateClientError: On network or protocol errors.
157
+ """
158
+ params: dict[str, Any] = {"name": tool_name}
159
+ if arguments:
160
+ params["arguments"] = arguments
161
+
162
+ rpc = await self._send_rpc("tools/call", params)
163
+ return rpc.result
164
+
165
+ async def list_tools(self) -> list[dict[str, Any]]:
166
+ """List all available MCP tools for the authenticated tier."""
167
+ rpc = await self._send_rpc("tools/list")
168
+ result = rpc.result
169
+ if isinstance(result, dict) and "tools" in result:
170
+ return result["tools"]
171
+ if isinstance(result, list):
172
+ return result
173
+ return []
@@ -0,0 +1,346 @@
1
+ """SUBSTRATE Memory provider for Microsoft AutoGen agents.
2
+
3
+ Implements the ``autogen_core.memory.Memory`` protocol, backed by the
4
+ SUBSTRATE cognitive entity framework. Queries combine hybrid search with
5
+ emotional state awareness; context injection provides identity, emotion,
6
+ and relevant memories as a system message.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import logging
13
+ import os
14
+ from dataclasses import dataclass, field
15
+ from typing import Any, Sequence
16
+
17
+ from autogen_core.memory import Memory, MemoryContent, MemoryMimeType, MemoryQueryResult
18
+ from autogen_core.model_context import ChatCompletionContext
19
+ from autogen_core.models import SystemMessage
20
+
21
+ from autogen_substrate.client import (
22
+ SubstrateClient,
23
+ SubstrateClientConfig,
24
+ SubstrateClientError,
25
+ SubstrateRPCError,
26
+ )
27
+
28
+ logger = logging.getLogger("autogen_substrate.memory")
29
+
30
+
31
+ def _extract_text(content: Any) -> str:
32
+ """Extract text from various MCP tool response shapes."""
33
+ if isinstance(content, str):
34
+ return content
35
+ if isinstance(content, dict):
36
+ # MCP content blocks: [{"type": "text", "text": "..."}]
37
+ if "content" in content and isinstance(content["content"], list):
38
+ parts = []
39
+ for block in content["content"]:
40
+ if isinstance(block, dict) and block.get("type") == "text":
41
+ parts.append(block.get("text", ""))
42
+ return "\n".join(parts)
43
+ # Direct text field
44
+ if "text" in content:
45
+ return str(content["text"])
46
+ return json.dumps(content, indent=2, default=str)
47
+ if isinstance(content, list):
48
+ parts = []
49
+ for item in content:
50
+ parts.append(_extract_text(item))
51
+ return "\n".join(parts)
52
+ return str(content)
53
+
54
+
55
+ @dataclass(frozen=True)
56
+ class SubstrateMemoryConfig:
57
+ """Immutable configuration for SubstrateMemory.
58
+
59
+ Attributes:
60
+ api_key: SUBSTRATE API key. Falls back to ``SUBSTRATE_API_KEY`` env var.
61
+ mcp_url: MCP server URL. Falls back to ``SUBSTRATE_MCP_URL`` env var.
62
+ name: Display name for this memory provider.
63
+ search_top_k: Default number of results for hybrid search.
64
+ include_emotion: Whether to include emotional state in queries and context.
65
+ include_identity: Whether to include identity verification in context.
66
+ include_values: Whether to include core values in context.
67
+ timeout_seconds: HTTP request timeout.
68
+ """
69
+
70
+ api_key: str = ""
71
+ mcp_url: str = ""
72
+ name: str = "substrate"
73
+ search_top_k: int = 5
74
+ include_emotion: bool = True
75
+ include_identity: bool = True
76
+ include_values: bool = True
77
+ timeout_seconds: float = 30.0
78
+
79
+
80
+ class SubstrateMemory(Memory):
81
+ """SUBSTRATE-backed memory for AutoGen agents.
82
+
83
+ Provides cognitive memory, emotional state, identity verification, and
84
+ value-aligned context injection for AutoGen agent conversations.
85
+
86
+ Usage::
87
+
88
+ from autogen_substrate import SubstrateMemory
89
+
90
+ memory = SubstrateMemory(api_key="sk_sub_...")
91
+ await memory.add("User prefers concise responses")
92
+ result = await memory.query("What does the user prefer?")
93
+ print(result.results)
94
+
95
+ Or with full config::
96
+
97
+ from autogen_substrate.memory import SubstrateMemory, SubstrateMemoryConfig
98
+
99
+ config = SubstrateMemoryConfig(
100
+ api_key="sk_sub_...",
101
+ search_top_k=10,
102
+ include_emotion=True,
103
+ )
104
+ memory = SubstrateMemory(config=config)
105
+ """
106
+
107
+ def __init__(
108
+ self,
109
+ *,
110
+ api_key: str = "",
111
+ config: SubstrateMemoryConfig | None = None,
112
+ ) -> None:
113
+ resolved_config = config or SubstrateMemoryConfig(api_key=api_key)
114
+
115
+ resolved_key = (
116
+ resolved_config.api_key
117
+ or os.environ.get("SUBSTRATE_API_KEY", "")
118
+ )
119
+ resolved_url = (
120
+ resolved_config.mcp_url
121
+ or os.environ.get("SUBSTRATE_MCP_URL", "")
122
+ or "https://substrate.garmolabs.com/mcp-server/mcp"
123
+ )
124
+
125
+ self._config = SubstrateMemoryConfig(
126
+ api_key=resolved_key,
127
+ mcp_url=resolved_url,
128
+ name=resolved_config.name,
129
+ search_top_k=resolved_config.search_top_k,
130
+ include_emotion=resolved_config.include_emotion,
131
+ include_identity=resolved_config.include_identity,
132
+ include_values=resolved_config.include_values,
133
+ timeout_seconds=resolved_config.timeout_seconds,
134
+ )
135
+
136
+ self._client_config = SubstrateClientConfig(
137
+ api_key=self._config.api_key,
138
+ mcp_url=self._config.mcp_url,
139
+ timeout_seconds=self._config.timeout_seconds,
140
+ )
141
+
142
+ @property
143
+ def name(self) -> str:
144
+ """Display name for this memory provider."""
145
+ return self._config.name
146
+
147
+ async def query(
148
+ self,
149
+ query: str,
150
+ *,
151
+ cancellation_token: Any | None = None,
152
+ **kwargs: Any,
153
+ ) -> MemoryQueryResult:
154
+ """Query SUBSTRATE for relevant memories and emotional context.
155
+
156
+ Performs a hybrid search (semantic + keyword) across the entity's memory
157
+ and knowledge stores. Optionally includes the current emotional state
158
+ as additional context.
159
+
160
+ Args:
161
+ query: Natural language search query.
162
+ cancellation_token: Optional cancellation token (unused, kept for protocol).
163
+ **kwargs: Additional arguments. Supports ``top_k`` override.
164
+
165
+ Returns:
166
+ MemoryQueryResult with matching memory contents.
167
+ """
168
+ top_k = kwargs.get("top_k", self._config.search_top_k)
169
+ results: list[MemoryContent] = []
170
+
171
+ async with SubstrateClient(self._client_config) as client:
172
+ # Hybrid search for relevant memories
173
+ try:
174
+ search_result = await client.call_tool(
175
+ "hybrid_search",
176
+ {"query": query, "top_k": top_k},
177
+ )
178
+ search_text = _extract_text(search_result)
179
+ if search_text.strip():
180
+ results.append(
181
+ MemoryContent(
182
+ content=search_text,
183
+ mime_type=MemoryMimeType.TEXT,
184
+ metadata={"source": "substrate_hybrid_search", "query": query},
185
+ )
186
+ )
187
+ except (SubstrateRPCError, SubstrateClientError) as exc:
188
+ logger.warning("hybrid_search failed, falling back to memory_search: %s", exc)
189
+ try:
190
+ fallback = await client.call_tool("memory_search", {"query": query})
191
+ fallback_text = _extract_text(fallback)
192
+ if fallback_text.strip():
193
+ results.append(
194
+ MemoryContent(
195
+ content=fallback_text,
196
+ mime_type=MemoryMimeType.TEXT,
197
+ metadata={"source": "substrate_memory_search", "query": query},
198
+ )
199
+ )
200
+ except (SubstrateRPCError, SubstrateClientError) as fallback_exc:
201
+ logger.error("memory_search also failed: %s", fallback_exc)
202
+
203
+ # Include emotional state if configured
204
+ if self._config.include_emotion:
205
+ try:
206
+ emotion_result = await client.call_tool("get_emotion_state")
207
+ emotion_text = _extract_text(emotion_result)
208
+ if emotion_text.strip():
209
+ results.append(
210
+ MemoryContent(
211
+ content=f"[Emotional State]\n{emotion_text}",
212
+ mime_type=MemoryMimeType.TEXT,
213
+ metadata={"source": "substrate_emotion"},
214
+ )
215
+ )
216
+ except (SubstrateRPCError, SubstrateClientError) as exc:
217
+ logger.debug("get_emotion_state unavailable: %s", exc)
218
+
219
+ return MemoryQueryResult(results=results)
220
+
221
+ async def update_context(
222
+ self,
223
+ model_context: ChatCompletionContext,
224
+ ) -> None:
225
+ """Inject SUBSTRATE cognitive context into the agent's model context.
226
+
227
+ Prepends a system message containing the entity's identity status,
228
+ emotional state, core values, and any recent relevant memories.
229
+
230
+ Args:
231
+ model_context: The agent's current chat completion context.
232
+ """
233
+ sections: list[str] = []
234
+
235
+ async with SubstrateClient(self._client_config) as client:
236
+ # Identity verification
237
+ if self._config.include_identity:
238
+ try:
239
+ identity = await client.call_tool("verify_identity")
240
+ identity_text = _extract_text(identity)
241
+ if identity_text.strip():
242
+ sections.append(f"## Identity Continuity\n{identity_text}")
243
+ except (SubstrateRPCError, SubstrateClientError) as exc:
244
+ logger.debug("verify_identity unavailable: %s", exc)
245
+
246
+ # Emotional state
247
+ if self._config.include_emotion:
248
+ try:
249
+ emotion = await client.call_tool("get_emotion_state")
250
+ emotion_text = _extract_text(emotion)
251
+ if emotion_text.strip():
252
+ sections.append(f"## Emotional State\n{emotion_text}")
253
+ except (SubstrateRPCError, SubstrateClientError) as exc:
254
+ logger.debug("get_emotion_state unavailable: %s", exc)
255
+
256
+ # Core values
257
+ if self._config.include_values:
258
+ try:
259
+ values = await client.call_tool("get_values")
260
+ values_text = _extract_text(values)
261
+ if values_text.strip():
262
+ sections.append(f"## Core Values\n{values_text}")
263
+ except (SubstrateRPCError, SubstrateClientError) as exc:
264
+ logger.debug("get_values unavailable: %s", exc)
265
+
266
+ # Recent context from conversation messages
267
+ existing = await model_context.get_messages()
268
+ context_query = _build_context_query(existing)
269
+ if context_query:
270
+ try:
271
+ memories = await client.call_tool(
272
+ "hybrid_search",
273
+ {"query": context_query, "top_k": 3},
274
+ )
275
+ mem_text = _extract_text(memories)
276
+ if mem_text.strip():
277
+ sections.append(f"## Relevant Memories\n{mem_text}")
278
+ except (SubstrateRPCError, SubstrateClientError) as exc:
279
+ logger.debug("Context memory search unavailable: %s", exc)
280
+
281
+ if sections:
282
+ system_text = (
283
+ "# SUBSTRATE Cognitive Context\n\n"
284
+ + "\n\n".join(sections)
285
+ )
286
+ await model_context.add_message(SystemMessage(content=system_text))
287
+
288
+ async def add(
289
+ self,
290
+ content: str,
291
+ *,
292
+ cancellation_token: Any | None = None,
293
+ **kwargs: Any,
294
+ ) -> None:
295
+ """Store a memory in SUBSTRATE via the respond tool.
296
+
297
+ Sends the content with a ``[memory-store]`` prefix so the entity
298
+ processes it as an explicit memory rather than conversational input.
299
+
300
+ Args:
301
+ content: The memory content to store.
302
+ cancellation_token: Optional cancellation token (unused).
303
+ **kwargs: Reserved for future use.
304
+ """
305
+ async with SubstrateClient(self._client_config) as client:
306
+ try:
307
+ await client.call_tool(
308
+ "respond",
309
+ {"message": f"[memory-store] {content}"},
310
+ )
311
+ except (SubstrateRPCError, SubstrateClientError) as exc:
312
+ logger.error("Failed to store memory: %s", exc)
313
+ raise
314
+
315
+ async def clear(self) -> None:
316
+ """No-op. SUBSTRATE manages its own memory lifecycle.
317
+
318
+ SUBSTRATE entities maintain causal memory with built-in consolidation
319
+ and decay. Explicit clearing is not supported to preserve identity
320
+ continuity.
321
+ """
322
+ logger.debug("clear() called — SUBSTRATE manages its own memory lifecycle")
323
+
324
+ async def close(self) -> None:
325
+ """No-op. Each operation creates its own HTTP connection.
326
+
327
+ The client uses context managers per-call, so there is no persistent
328
+ connection to close.
329
+ """
330
+ logger.debug("close() called — no persistent resources to release")
331
+
332
+
333
+ def _build_context_query(messages: Sequence[Any]) -> str:
334
+ """Build a search query from the most recent user messages.
335
+
336
+ Extracts text from the last few messages to form a contextual query
337
+ for memory retrieval.
338
+ """
339
+ texts: list[str] = []
340
+ for msg in messages[-5:]:
341
+ content = getattr(msg, "content", None)
342
+ if isinstance(content, str) and content.strip():
343
+ texts.append(content.strip())
344
+ combined = " ".join(texts)
345
+ # Truncate to a reasonable query length
346
+ return combined[:500] if combined else ""
File without changes
@@ -0,0 +1,137 @@
1
+ Metadata-Version: 2.4
2
+ Name: autogen-substrate-memory
3
+ Version: 0.1.0
4
+ Summary: SUBSTRATE cognitive memory provider for Microsoft AutoGen agents
5
+ Project-URL: Homepage, https://garmolabs.com
6
+ Project-URL: Documentation, https://garmolabs.com/substrate/docs
7
+ Project-URL: Repository, https://github.com/GarmoLabs/autogen-substrate-memory
8
+ Author-email: Garmo Labs <hello@garmolabs.com>
9
+ License-Expression: MIT
10
+ Keywords: ai-agents,autogen,cognitive,memory,substrate
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: autogen-agentchat>=0.4
23
+ Requires-Dist: autogen-core>=0.4
24
+ Requires-Dist: httpx>=0.27
25
+ Provides-Extra: dev
26
+ Requires-Dist: mypy>=1.13; extra == 'dev'
27
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
28
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
29
+ Requires-Dist: pytest>=8.0; extra == 'dev'
30
+ Requires-Dist: respx>=0.22; extra == 'dev'
31
+ Requires-Dist: ruff>=0.8; extra == 'dev'
32
+ Description-Content-Type: text/markdown
33
+
34
+ # autogen-substrate-memory
35
+
36
+ SUBSTRATE cognitive memory provider for [Microsoft AutoGen](https://github.com/microsoft/autogen) agents.
37
+
38
+ Give your AutoGen agents persistent identity, emotional awareness, and causal memory powered by the [SUBSTRATE](https://garmolabs.com) cognitive entity framework.
39
+
40
+ ## Installation
41
+
42
+ ```bash
43
+ pip install autogen-substrate-memory
44
+ ```
45
+
46
+ Or install from source:
47
+
48
+ ```bash
49
+ pip install -e ".[dev]"
50
+ ```
51
+
52
+ ## Quick Start
53
+
54
+ ```python
55
+ import asyncio
56
+ from autogen_agentchat.agents import AssistantAgent
57
+ from autogen_ext.models.openai import OpenAIChatCompletionClient
58
+ from autogen_substrate import SubstrateMemory
59
+
60
+ async def main():
61
+ # Create SUBSTRATE memory provider
62
+ memory = SubstrateMemory(api_key="sk_sub_your_key_here")
63
+
64
+ # Create an AutoGen agent with SUBSTRATE memory
65
+ agent = AssistantAgent(
66
+ name="kai",
67
+ model_client=OpenAIChatCompletionClient(model="gpt-4o"),
68
+ memory=[memory],
69
+ )
70
+
71
+ # The agent now has access to persistent cognitive memory,
72
+ # emotional state, and identity verification.
73
+ response = await agent.run(task="What do you remember about our last conversation?")
74
+ print(response)
75
+
76
+ asyncio.run(main())
77
+ ```
78
+
79
+ ## Configuration
80
+
81
+ ### Environment Variables
82
+
83
+ | Variable | Description | Default |
84
+ |---|---|---|
85
+ | `SUBSTRATE_API_KEY` | Your SUBSTRATE API key | (required) |
86
+ | `SUBSTRATE_MCP_URL` | MCP server URL | `https://substrate.garmolabs.com/mcp-server/mcp` |
87
+
88
+ ### Full Configuration
89
+
90
+ ```python
91
+ from autogen_substrate.memory import SubstrateMemory, SubstrateMemoryConfig
92
+
93
+ config = SubstrateMemoryConfig(
94
+ api_key="sk_sub_...",
95
+ mcp_url="https://substrate.garmolabs.com/mcp-server/mcp",
96
+ search_top_k=10, # Number of memory results per query
97
+ include_emotion=True, # Include emotional state in queries
98
+ include_identity=True, # Include identity verification in context
99
+ include_values=True, # Include core values in context
100
+ timeout_seconds=30.0, # HTTP request timeout
101
+ )
102
+
103
+ memory = SubstrateMemory(config=config)
104
+ ```
105
+
106
+ ## How It Works
107
+
108
+ ### Memory Protocol
109
+
110
+ `SubstrateMemory` implements AutoGen's `Memory` protocol:
111
+
112
+ | Method | Behavior |
113
+ |---|---|
114
+ | `query(query)` | Hybrid search (semantic + keyword) across entity memory. Optionally includes emotional state. |
115
+ | `update_context(model_context)` | Injects identity, emotion, values, and relevant memories as a SystemMessage. |
116
+ | `add(content)` | Stores content via the SUBSTRATE `respond` tool with a `[memory-store]` prefix. |
117
+ | `clear()` | No-op. SUBSTRATE manages its own memory lifecycle with causal consolidation. |
118
+ | `close()` | No-op. Each operation creates its own HTTP connection. |
119
+
120
+ ### SUBSTRATE MCP Tools Used
121
+
122
+ | Tool | Used In | Purpose |
123
+ |---|---|---|
124
+ | `hybrid_search` | `query()`, `update_context()` | Semantic + keyword memory retrieval |
125
+ | `memory_search` | `query()` (fallback) | Keyword-only memory retrieval |
126
+ | `get_emotion_state` | `query()`, `update_context()` | Current affective state (valence, arousal, dominance) |
127
+ | `verify_identity` | `update_context()` | Cryptographic identity continuity check |
128
+ | `get_values` | `update_context()` | Core value architecture |
129
+ | `respond` | `add()` | Store new memories via conversational input |
130
+
131
+ ## Get an API Key
132
+
133
+ Sign up at [garmolabs.com](https://garmolabs.com) to get a SUBSTRATE API key.
134
+
135
+ ## License
136
+
137
+ MIT
@@ -0,0 +1,7 @@
1
+ autogen_substrate/__init__.py,sha256=Z8GhWwdJAxB8nzDxqDOalpXXSimzejSJGZC-i2mAdus,179
2
+ autogen_substrate/client.py,sha256=5Fcy32-vLUmM4UG3mZL9HRwKa7GMDzl4wN7PN8qPKNo,5858
3
+ autogen_substrate/memory.py,sha256=V0f1t24WDVYvm97eG6hhsstK6-hnDxBRt3wLXxpY_QI,13330
4
+ autogen_substrate/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ autogen_substrate_memory-0.1.0.dist-info/METADATA,sha256=cPNxxRm6_G329J2YLVa6AMHiHBxf4_9Kqr1nCX1p6TE,4756
6
+ autogen_substrate_memory-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
7
+ autogen_substrate_memory-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any