memos-ai 0.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.
memos_ai-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 memos
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,19 @@
1
+ include README.md
2
+ include LICENSE
3
+ include pyproject.toml
4
+
5
+ global-exclude .env
6
+ global-exclude node_modules
7
+ global-exclude node_modules/*
8
+ global-exclude venv
9
+ global-exclude venv/*
10
+ global-exclude dist
11
+ global-exclude dist/*
12
+ global-exclude __pycache__
13
+ global-exclude __pycache__/*
14
+ global-exclude .pytest_cache
15
+ global-exclude .pytest_cache/*
16
+ global-exclude coverage
17
+ global-exclude coverage/*
18
+ global-exclude *.log
19
+ global-exclude *.sqlite
@@ -0,0 +1,120 @@
1
+ Metadata-Version: 2.4
2
+ Name: memos-ai
3
+ Version: 0.1.0
4
+ Summary: Python SDK for memos — persistent brain framework for AI agents
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://memos.io
7
+ Project-URL: Repository, https://github.com/memos/memos-py
8
+ Project-URL: Documentation, https://docs.memos.io
9
+ Requires-Python: <4.0,>=3.11
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: httpx>=0.24.0
13
+ Provides-Extra: test
14
+ Requires-Dist: pytest>=7.0; extra == "test"
15
+ Requires-Dist: pytest-mock>=3.0; extra == "test"
16
+ Dynamic: license-file
17
+
18
+ # MEMOS Python SDK
19
+
20
+ Python SDK for [MEMOS](https://memos.io) — persistent brain framework for AI agents.
21
+
22
+ ## Installation
23
+ ```bash
24
+ pip install memos-ai
25
+ ```
26
+
27
+ ## Publish Verification
28
+ ```bash
29
+ python3 -m venv verify
30
+ source verify/bin/activate
31
+ pip install memos-ai
32
+ python3
33
+ ```
34
+
35
+ ```python
36
+ from memos import MemosClient
37
+
38
+ print(MemosClient)
39
+ ```
40
+
41
+ Expected output:
42
+ ```text
43
+ <class 'memos.client.MemosClient'>
44
+ ```
45
+
46
+ ## Quick Start
47
+ ```python
48
+ from memos import MemosClient
49
+
50
+ client = MemosClient(
51
+ api_key="your_api_key",
52
+ agent_id="your_agent_id"
53
+ )
54
+
55
+ # Store a memory
56
+ memory = client.store_memory(
57
+ content="User prefers Python over JavaScript",
58
+ type="semantic",
59
+ importance=4
60
+ )
61
+
62
+ # Search memories
63
+ results = client.search("language preferences")
64
+ for r in results:
65
+ print(f"{r.score:.2f} — {r.content}")
66
+
67
+ # Ask with memory context
68
+ response = client.query("What language does this user prefer?")
69
+ print(response.answer)
70
+
71
+ # Trigger dream consolidation
72
+ dream = client.trigger_dream()
73
+ print(f"Created {dream.new_memories_created} new memories")
74
+ ```
75
+
76
+ ## Authentication
77
+ Get your API key and agent ID from https://memos.io/dashboard
78
+
79
+ ## Methods Reference
80
+
81
+ | Method | Parameters | Returns | Description |
82
+ |---|---|---|---|
83
+ | `store_memory` | `content` (str), `type` (str="episodic"), `importance` (int=3), `tags` (list[str]=None) | `Memory` | Store a new memory for the agent |
84
+ | `list_memories` | None | `list[Memory]` | List all memories for the agent |
85
+ | `delete_memory` | `memory_id` (str) | `bool` | Delete a specific memory by ID |
86
+ | `search` | `query` (str), `search_type` (str="keyword"), `limit` (int=10) | `list[SearchResult]` | Search agent memories |
87
+ | `query` | `question` (str), `include_sources` (bool=True), `conversation_history` (list[dict]=None) | `RAGResponse` | Ask a question using RAG |
88
+ | `trigger_dream` | None | `DreamResult` | Trigger a dream consolidation cycle |
89
+ | `list_skills` | None | `list[Skill]` | List all available skills in the marketplace |
90
+ | `execute_skill` | `skill_id` (str), `input` (str) | `SkillResult` | Execute a skill |
91
+ | `run_pipeline` | `steps` (list[dict]), `input` (str) | `dict` | Run a multi-step pipeline |
92
+ | `get_identity` | None | `dict` | Get the agent's identity and reputation |
93
+
94
+ ## Error Handling
95
+ ```python
96
+ from memos import MemosError, AuthError, RateLimitError
97
+
98
+ try:
99
+ client.store_memory("...")
100
+ except AuthError:
101
+ print("Check your API key at memos.io/profile")
102
+ except RateLimitError:
103
+ print("Rate limit hit — slow down requests")
104
+ except MemosError as e:
105
+ print(f"API error {e.status_code}: {e.message}")
106
+ ```
107
+
108
+ ## Context Manager
109
+ ```python
110
+ with MemosClient(api_key="...", agent_id="...") as client:
111
+ client.store_memory("...")
112
+ # HTTP connection closed automatically
113
+ ```
114
+
115
+ ## Requirements
116
+ Python 3.8+
117
+ No additional dependencies beyond `httpx`.
118
+
119
+ ## License
120
+ MIT
@@ -0,0 +1,103 @@
1
+ # MEMOS Python SDK
2
+
3
+ Python SDK for [MEMOS](https://memos.io) — persistent brain framework for AI agents.
4
+
5
+ ## Installation
6
+ ```bash
7
+ pip install memos-ai
8
+ ```
9
+
10
+ ## Publish Verification
11
+ ```bash
12
+ python3 -m venv verify
13
+ source verify/bin/activate
14
+ pip install memos-ai
15
+ python3
16
+ ```
17
+
18
+ ```python
19
+ from memos import MemosClient
20
+
21
+ print(MemosClient)
22
+ ```
23
+
24
+ Expected output:
25
+ ```text
26
+ <class 'memos.client.MemosClient'>
27
+ ```
28
+
29
+ ## Quick Start
30
+ ```python
31
+ from memos import MemosClient
32
+
33
+ client = MemosClient(
34
+ api_key="your_api_key",
35
+ agent_id="your_agent_id"
36
+ )
37
+
38
+ # Store a memory
39
+ memory = client.store_memory(
40
+ content="User prefers Python over JavaScript",
41
+ type="semantic",
42
+ importance=4
43
+ )
44
+
45
+ # Search memories
46
+ results = client.search("language preferences")
47
+ for r in results:
48
+ print(f"{r.score:.2f} — {r.content}")
49
+
50
+ # Ask with memory context
51
+ response = client.query("What language does this user prefer?")
52
+ print(response.answer)
53
+
54
+ # Trigger dream consolidation
55
+ dream = client.trigger_dream()
56
+ print(f"Created {dream.new_memories_created} new memories")
57
+ ```
58
+
59
+ ## Authentication
60
+ Get your API key and agent ID from https://memos.io/dashboard
61
+
62
+ ## Methods Reference
63
+
64
+ | Method | Parameters | Returns | Description |
65
+ |---|---|---|---|
66
+ | `store_memory` | `content` (str), `type` (str="episodic"), `importance` (int=3), `tags` (list[str]=None) | `Memory` | Store a new memory for the agent |
67
+ | `list_memories` | None | `list[Memory]` | List all memories for the agent |
68
+ | `delete_memory` | `memory_id` (str) | `bool` | Delete a specific memory by ID |
69
+ | `search` | `query` (str), `search_type` (str="keyword"), `limit` (int=10) | `list[SearchResult]` | Search agent memories |
70
+ | `query` | `question` (str), `include_sources` (bool=True), `conversation_history` (list[dict]=None) | `RAGResponse` | Ask a question using RAG |
71
+ | `trigger_dream` | None | `DreamResult` | Trigger a dream consolidation cycle |
72
+ | `list_skills` | None | `list[Skill]` | List all available skills in the marketplace |
73
+ | `execute_skill` | `skill_id` (str), `input` (str) | `SkillResult` | Execute a skill |
74
+ | `run_pipeline` | `steps` (list[dict]), `input` (str) | `dict` | Run a multi-step pipeline |
75
+ | `get_identity` | None | `dict` | Get the agent's identity and reputation |
76
+
77
+ ## Error Handling
78
+ ```python
79
+ from memos import MemosError, AuthError, RateLimitError
80
+
81
+ try:
82
+ client.store_memory("...")
83
+ except AuthError:
84
+ print("Check your API key at memos.io/profile")
85
+ except RateLimitError:
86
+ print("Rate limit hit — slow down requests")
87
+ except MemosError as e:
88
+ print(f"API error {e.status_code}: {e.message}")
89
+ ```
90
+
91
+ ## Context Manager
92
+ ```python
93
+ with MemosClient(api_key="...", agent_id="...") as client:
94
+ client.store_memory("...")
95
+ # HTTP connection closed automatically
96
+ ```
97
+
98
+ ## Requirements
99
+ Python 3.8+
100
+ No additional dependencies beyond `httpx`.
101
+
102
+ ## License
103
+ MIT
@@ -0,0 +1,22 @@
1
+ """memos-py — Python SDK for memos."""
2
+ from __future__ import annotations
3
+
4
+ from .client import MemosClient
5
+ from .exceptions import AuthError, MemosError, NotFoundError, RateLimitError, ServerError
6
+ from .models import DreamResult, Memory, RAGResponse, SearchResult, Skill, SkillResult
7
+
8
+ __version__ = "0.1.0"
9
+ __all__ = [
10
+ "MemosClient",
11
+ "Memory",
12
+ "SearchResult",
13
+ "RAGResponse",
14
+ "Skill",
15
+ "SkillResult",
16
+ "DreamResult",
17
+ "MemosError",
18
+ "AuthError",
19
+ "RateLimitError",
20
+ "NotFoundError",
21
+ "ServerError",
22
+ ]
@@ -0,0 +1,337 @@
1
+ """Synchronous HTTP client for the memos API."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Dict, List, Optional
5
+
6
+ import httpx
7
+
8
+ from .exceptions import AuthError, MemosError, NotFoundError, RateLimitError, ServerError
9
+ from .models import DreamResult, Memory, RAGResponse, SearchResult, Skill, SkillResult
10
+
11
+
12
+ class MemosClient:
13
+ """Python client for the memos API.
14
+
15
+ Usage::
16
+
17
+ from memos import MemosClient
18
+
19
+ client = MemosClient(
20
+ api_key="mk0s_your_key",
21
+ agent_id="your_agent_id",
22
+ base_url="https://memos.io"
23
+ )
24
+
25
+ with MemosClient(api_key=..., agent_id=...) as client:
26
+ client.store_memory("...")
27
+ """
28
+
29
+ def __init__(
30
+ self,
31
+ api_key: str,
32
+ agent_id: str,
33
+ base_url: str = "https://memos.io",
34
+ timeout: float = 30.0,
35
+ ) -> None:
36
+ if not api_key or not api_key.strip():
37
+ raise AuthError("api_key is required", status_code=None)
38
+ if not agent_id or not agent_id.strip():
39
+ raise MemosError("agent_id is required", status_code=None)
40
+
41
+ self.api_key = api_key
42
+ self.agent_id = agent_id
43
+ self.base_url = base_url.rstrip("/")
44
+ self._client = httpx.Client(
45
+ base_url=self.base_url,
46
+ timeout=timeout,
47
+ headers={
48
+ "Authorization": "Bearer {0}".format(api_key),
49
+ "Content-Type": "application/json",
50
+ "User-Agent": "memos-py/0.1.0",
51
+ },
52
+ )
53
+
54
+ # ------------------------------------------------------------------
55
+ # Internal helpers
56
+ # ------------------------------------------------------------------
57
+
58
+ def _handle_response(self, response: httpx.Response) -> Dict: # type: ignore[type-arg]
59
+ """Parse response and raise typed exceptions on errors."""
60
+ if response.status_code == 401:
61
+ raise AuthError("Invalid or missing API key", 401)
62
+ if response.status_code == 429:
63
+ raise RateLimitError("Rate limit exceeded", 429)
64
+ if response.status_code == 404:
65
+ raise NotFoundError("Resource not found", 404)
66
+ if response.status_code >= 500:
67
+ raise ServerError("Server error: {0}".format(response.text), response.status_code)
68
+ if not response.is_success:
69
+ raise MemosError("Request failed: {0}".format(response.text), response.status_code)
70
+ return response.json() # type: ignore[no-any-return]
71
+
72
+ @staticmethod
73
+ def _build_memory(data: Dict) -> Memory: # type: ignore[type-arg]
74
+ """Safely build a Memory from a response dict, ignoring unknown keys."""
75
+ known = Memory.__dataclass_fields__
76
+ filtered = {k: v for k, v in data.items() if k in known}
77
+ # Map camelCase keys from API
78
+ if "agentId" in data and "agent_id" not in filtered:
79
+ filtered["agent_id"] = data["agentId"]
80
+ if "createdAt" in data and "created_at" not in filtered:
81
+ filtered["created_at"] = data["createdAt"]
82
+ if "accessCount" in data and "access_count" not in filtered:
83
+ filtered["access_count"] = data["accessCount"]
84
+ if "decayScore" in data and "decay_score" not in filtered:
85
+ filtered["decay_score"] = data["decayScore"]
86
+ return Memory(**filtered)
87
+
88
+ # ------------------------------------------------------------------
89
+ # Public API
90
+ # ------------------------------------------------------------------
91
+
92
+ def store_memory(
93
+ self,
94
+ content: str,
95
+ type: str = "episodic",
96
+ importance: int = 3,
97
+ tags: Optional[List[str]] = None,
98
+ ) -> Memory:
99
+ """Store a new memory for the agent.
100
+
101
+ Args:
102
+ content: The text content to store.
103
+ type: Memory type — "episodic", "semantic", or "procedural".
104
+ importance: Importance score from 1 to 5.
105
+ tags: Optional list of string tags.
106
+
107
+ Returns:
108
+ The created Memory object.
109
+ """
110
+ response = self._client.post(
111
+ "/api/memory",
112
+ json={
113
+ "agentId": self.agent_id,
114
+ "content": content,
115
+ "type": type,
116
+ "importance": importance,
117
+ "tags": tags or [],
118
+ },
119
+ )
120
+ data = self._handle_response(response)
121
+ memory_data = data.get("memory", data)
122
+ return self._build_memory(memory_data)
123
+
124
+ def list_memories(self) -> List[Memory]:
125
+ """List all memories for the agent.
126
+
127
+ Returns:
128
+ A list of Memory objects.
129
+ """
130
+ response = self._client.get(
131
+ "/api/memory",
132
+ params={"agentId": self.agent_id},
133
+ )
134
+ data = self._handle_response(response)
135
+ items = data.get("memories", data) if isinstance(data, dict) else data
136
+ if not isinstance(items, list):
137
+ items = []
138
+ return [self._build_memory(m) for m in items]
139
+
140
+ def delete_memory(self, memory_id: str) -> bool:
141
+ """Delete a specific memory by ID.
142
+
143
+ Args:
144
+ memory_id: The ID of the memory to delete.
145
+
146
+ Returns:
147
+ True if deletion was successful.
148
+ """
149
+ response = self._client.delete("/api/memory/{0}".format(memory_id))
150
+ self._handle_response(response)
151
+ return True
152
+
153
+ def search(
154
+ self,
155
+ query: str,
156
+ search_type: str = "keyword",
157
+ limit: int = 10,
158
+ ) -> List[SearchResult]:
159
+ """Search agent memories.
160
+
161
+ Args:
162
+ query: The search query string.
163
+ search_type: "keyword" or "semantic".
164
+ limit: Maximum number of results.
165
+
166
+ Returns:
167
+ A list of SearchResult objects.
168
+ """
169
+ response = self._client.post(
170
+ "/api/search",
171
+ json={
172
+ "agentId": self.agent_id,
173
+ "query": query,
174
+ "searchType": search_type,
175
+ "limit": limit,
176
+ },
177
+ )
178
+ data = self._handle_response(response)
179
+ items = data.get("results", data) if isinstance(data, dict) else data
180
+ if not isinstance(items, list):
181
+ items = []
182
+ return [
183
+ SearchResult(
184
+ id=r.get("id", ""),
185
+ content=r.get("content", ""),
186
+ type=r.get("type", ""),
187
+ importance=r.get("importance", 0),
188
+ score=r.get("score", 0.0),
189
+ tags=r.get("tags", []),
190
+ )
191
+ for r in items
192
+ ]
193
+
194
+ def query(
195
+ self,
196
+ question: str,
197
+ include_sources: bool = True,
198
+ conversation_history: Optional[List[dict]] = None, # type: ignore[type-arg]
199
+ ) -> RAGResponse:
200
+ """Ask a question using RAG (retrieval-augmented generation).
201
+
202
+ Args:
203
+ question: The question to ask.
204
+ include_sources: Whether to include source memories.
205
+ conversation_history: Optional prior conversation turns.
206
+
207
+ Returns:
208
+ A RAGResponse with answer, sources, and confidence.
209
+ """
210
+ response = self._client.post(
211
+ "/api/rag",
212
+ json={
213
+ "agentId": self.agent_id,
214
+ "query": question,
215
+ "conversationHistory": conversation_history or [],
216
+ },
217
+ )
218
+ data = self._handle_response(response)
219
+ return RAGResponse(
220
+ answer=data.get("answer", ""),
221
+ sources=data.get("sources", []),
222
+ confidence=data.get("confidence", 0.0),
223
+ )
224
+
225
+ def trigger_dream(self) -> DreamResult:
226
+ """Trigger a dream consolidation cycle for the agent.
227
+
228
+ Returns:
229
+ A DreamResult with consolidation details.
230
+ """
231
+ response = self._client.post(
232
+ "/api/agent/{0}/dreams".format(self.agent_id),
233
+ json={},
234
+ )
235
+ data = self._handle_response(response)
236
+ return DreamResult(
237
+ memories_analyzed=data.get("memoriesAnalyzed", 0),
238
+ patterns_found=data.get("patternsFound", 0),
239
+ new_memories_created=data.get("newMemoriesCreated", 0),
240
+ dream_summary=data.get("dreamSummary", ""),
241
+ new_memories=data.get("newMemories", []),
242
+ duration=data.get("duration", 0),
243
+ )
244
+
245
+ def list_skills(self) -> List[Skill]:
246
+ """List all available skills in the marketplace.
247
+
248
+ Returns:
249
+ A list of Skill objects.
250
+ """
251
+ response = self._client.get("/api/skills")
252
+ data = self._handle_response(response)
253
+ items = data.get("skills", data) if isinstance(data, dict) else data
254
+ if not isinstance(items, list):
255
+ items = []
256
+ return [
257
+ Skill(
258
+ id=s.get("id", ""),
259
+ name=s.get("name", ""),
260
+ description=s.get("description", ""),
261
+ category=s.get("category", ""),
262
+ price=s.get("price", 0.0),
263
+ publisher=s.get("publisher", ""),
264
+ )
265
+ for s in items
266
+ ]
267
+
268
+ def execute_skill(self, skill_id: str, input: str) -> SkillResult:
269
+ """Execute a skill.
270
+
271
+ Args:
272
+ skill_id: The ID of the skill to execute.
273
+ input: The input string for the skill.
274
+
275
+ Returns:
276
+ A SkillResult with execution details.
277
+ """
278
+ response = self._client.post(
279
+ "/api/execute",
280
+ json={
281
+ "agentId": self.agent_id,
282
+ "skillId": skill_id,
283
+ "input": input,
284
+ },
285
+ )
286
+ data = self._handle_response(response)
287
+ return SkillResult(
288
+ skill_id=data.get("skillId", skill_id),
289
+ result=data.get("result", ""),
290
+ tokens_used=data.get("tokensUsed", 0),
291
+ duration=data.get("duration", 0),
292
+ )
293
+
294
+ def run_pipeline(self, steps: List[dict], input: str) -> dict: # type: ignore[type-arg]
295
+ """Run a multi-step pipeline.
296
+
297
+ Args:
298
+ steps: List of step dicts, each with at least a "skillId" key.
299
+ input: The initial input string.
300
+
301
+ Returns:
302
+ Raw response dict (pipeline responses are complex).
303
+ """
304
+ response = self._client.post(
305
+ "/api/pipeline",
306
+ json={
307
+ "agentId": self.agent_id,
308
+ "steps": steps,
309
+ "input": input,
310
+ },
311
+ )
312
+ return self._handle_response(response)
313
+
314
+ def get_identity(self) -> dict: # type: ignore[type-arg]
315
+ """Get the agent's identity and reputation.
316
+
317
+ Returns:
318
+ Raw response dict with reputation data.
319
+ """
320
+ response = self._client.get(
321
+ "/api/agent/{0}/reputation".format(self.agent_id),
322
+ )
323
+ return self._handle_response(response)
324
+
325
+ # ------------------------------------------------------------------
326
+ # Lifecycle
327
+ # ------------------------------------------------------------------
328
+
329
+ def close(self) -> None:
330
+ """Close the underlying HTTP connection."""
331
+ self._client.close()
332
+
333
+ def __enter__(self) -> "MemosClient":
334
+ return self
335
+
336
+ def __exit__(self, *args: object) -> None:
337
+ self.close()
@@ -0,0 +1,27 @@
1
+ """memos-py exception hierarchy."""
2
+ from __future__ import annotations
3
+
4
+
5
+ class MemosError(Exception):
6
+ """Base exception for all memos SDK errors."""
7
+
8
+ def __init__(self, message: str, status_code: int | None = None) -> None:
9
+ self.message = message
10
+ self.status_code = status_code
11
+ super().__init__(message)
12
+
13
+
14
+ class AuthError(MemosError):
15
+ """Raised when authentication fails (HTTP 401)."""
16
+
17
+
18
+ class RateLimitError(MemosError):
19
+ """Raised when rate limit is exceeded (HTTP 429)."""
20
+
21
+
22
+ class NotFoundError(MemosError):
23
+ """Raised when a resource is not found (HTTP 404)."""
24
+
25
+
26
+ class ServerError(MemosError):
27
+ """Raised when the server returns a 5xx error."""
@@ -0,0 +1,75 @@
1
+ """memos-py data models using Python dataclasses."""
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass, field
5
+ from typing import List, Optional
6
+
7
+
8
+ @dataclass
9
+ class Memory:
10
+ """Represents a stored memory."""
11
+
12
+ id: str
13
+ agent_id: str
14
+ content: str
15
+ type: str
16
+ importance: int
17
+ tags: List[str] = field(default_factory=list)
18
+ created_at: Optional[str] = None
19
+ access_count: int = 0
20
+ decay_score: float = 1.0
21
+
22
+
23
+ @dataclass
24
+ class SearchResult:
25
+ """A single search result."""
26
+
27
+ id: str
28
+ content: str
29
+ type: str
30
+ importance: int
31
+ score: float
32
+ tags: List[str] = field(default_factory=list)
33
+
34
+
35
+ @dataclass
36
+ class RAGResponse:
37
+ """Response from a RAG (retrieval-augmented generation) query."""
38
+
39
+ answer: str
40
+ sources: List[dict] # type: ignore[type-arg]
41
+ confidence: float
42
+
43
+
44
+ @dataclass
45
+ class Skill:
46
+ """A skill available in the marketplace."""
47
+
48
+ id: str
49
+ name: str
50
+ description: str
51
+ category: str
52
+ price: float
53
+ publisher: str
54
+
55
+
56
+ @dataclass
57
+ class SkillResult:
58
+ """Result of executing a skill."""
59
+
60
+ skill_id: str
61
+ result: str
62
+ tokens_used: int
63
+ duration: int
64
+
65
+
66
+ @dataclass
67
+ class DreamResult:
68
+ """Result of a dream consolidation cycle."""
69
+
70
+ memories_analyzed: int
71
+ patterns_found: int
72
+ new_memories_created: int
73
+ dream_summary: str
74
+ new_memories: List[dict] # type: ignore[type-arg]
75
+ duration: int
@@ -0,0 +1,120 @@
1
+ Metadata-Version: 2.4
2
+ Name: memos-ai
3
+ Version: 0.1.0
4
+ Summary: Python SDK for memos — persistent brain framework for AI agents
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://memos.io
7
+ Project-URL: Repository, https://github.com/memos/memos-py
8
+ Project-URL: Documentation, https://docs.memos.io
9
+ Requires-Python: <4.0,>=3.11
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: httpx>=0.24.0
13
+ Provides-Extra: test
14
+ Requires-Dist: pytest>=7.0; extra == "test"
15
+ Requires-Dist: pytest-mock>=3.0; extra == "test"
16
+ Dynamic: license-file
17
+
18
+ # MEMOS Python SDK
19
+
20
+ Python SDK for [MEMOS](https://memos.io) — persistent brain framework for AI agents.
21
+
22
+ ## Installation
23
+ ```bash
24
+ pip install memos-ai
25
+ ```
26
+
27
+ ## Publish Verification
28
+ ```bash
29
+ python3 -m venv verify
30
+ source verify/bin/activate
31
+ pip install memos-ai
32
+ python3
33
+ ```
34
+
35
+ ```python
36
+ from memos import MemosClient
37
+
38
+ print(MemosClient)
39
+ ```
40
+
41
+ Expected output:
42
+ ```text
43
+ <class 'memos.client.MemosClient'>
44
+ ```
45
+
46
+ ## Quick Start
47
+ ```python
48
+ from memos import MemosClient
49
+
50
+ client = MemosClient(
51
+ api_key="your_api_key",
52
+ agent_id="your_agent_id"
53
+ )
54
+
55
+ # Store a memory
56
+ memory = client.store_memory(
57
+ content="User prefers Python over JavaScript",
58
+ type="semantic",
59
+ importance=4
60
+ )
61
+
62
+ # Search memories
63
+ results = client.search("language preferences")
64
+ for r in results:
65
+ print(f"{r.score:.2f} — {r.content}")
66
+
67
+ # Ask with memory context
68
+ response = client.query("What language does this user prefer?")
69
+ print(response.answer)
70
+
71
+ # Trigger dream consolidation
72
+ dream = client.trigger_dream()
73
+ print(f"Created {dream.new_memories_created} new memories")
74
+ ```
75
+
76
+ ## Authentication
77
+ Get your API key and agent ID from https://memos.io/dashboard
78
+
79
+ ## Methods Reference
80
+
81
+ | Method | Parameters | Returns | Description |
82
+ |---|---|---|---|
83
+ | `store_memory` | `content` (str), `type` (str="episodic"), `importance` (int=3), `tags` (list[str]=None) | `Memory` | Store a new memory for the agent |
84
+ | `list_memories` | None | `list[Memory]` | List all memories for the agent |
85
+ | `delete_memory` | `memory_id` (str) | `bool` | Delete a specific memory by ID |
86
+ | `search` | `query` (str), `search_type` (str="keyword"), `limit` (int=10) | `list[SearchResult]` | Search agent memories |
87
+ | `query` | `question` (str), `include_sources` (bool=True), `conversation_history` (list[dict]=None) | `RAGResponse` | Ask a question using RAG |
88
+ | `trigger_dream` | None | `DreamResult` | Trigger a dream consolidation cycle |
89
+ | `list_skills` | None | `list[Skill]` | List all available skills in the marketplace |
90
+ | `execute_skill` | `skill_id` (str), `input` (str) | `SkillResult` | Execute a skill |
91
+ | `run_pipeline` | `steps` (list[dict]), `input` (str) | `dict` | Run a multi-step pipeline |
92
+ | `get_identity` | None | `dict` | Get the agent's identity and reputation |
93
+
94
+ ## Error Handling
95
+ ```python
96
+ from memos import MemosError, AuthError, RateLimitError
97
+
98
+ try:
99
+ client.store_memory("...")
100
+ except AuthError:
101
+ print("Check your API key at memos.io/profile")
102
+ except RateLimitError:
103
+ print("Rate limit hit — slow down requests")
104
+ except MemosError as e:
105
+ print(f"API error {e.status_code}: {e.message}")
106
+ ```
107
+
108
+ ## Context Manager
109
+ ```python
110
+ with MemosClient(api_key="...", agent_id="...") as client:
111
+ client.store_memory("...")
112
+ # HTTP connection closed automatically
113
+ ```
114
+
115
+ ## Requirements
116
+ Python 3.8+
117
+ No additional dependencies beyond `httpx`.
118
+
119
+ ## License
120
+ MIT
@@ -0,0 +1,14 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ pyproject.toml
5
+ memos/__init__.py
6
+ memos/client.py
7
+ memos/exceptions.py
8
+ memos/models.py
9
+ memos_ai.egg-info/PKG-INFO
10
+ memos_ai.egg-info/SOURCES.txt
11
+ memos_ai.egg-info/dependency_links.txt
12
+ memos_ai.egg-info/requires.txt
13
+ memos_ai.egg-info/top_level.txt
14
+ tests/test_client.py
@@ -0,0 +1,5 @@
1
+ httpx>=0.24.0
2
+
3
+ [test]
4
+ pytest>=7.0
5
+ pytest-mock>=3.0
@@ -0,0 +1 @@
1
+ memos
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "memos-ai"
7
+ version = "0.1.0"
8
+ description = "Python SDK for memos — persistent brain framework for AI agents"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.11,<4.0"
13
+ dependencies = [
14
+ "httpx>=0.24.0"
15
+ ]
16
+
17
+ [project.optional-dependencies]
18
+ test = ["pytest>=7.0", "pytest-mock>=3.0"]
19
+
20
+ [project.urls]
21
+ Homepage = "https://memos.io"
22
+ Repository = "https://github.com/memos/memos-py"
23
+ Documentation = "https://docs.memos.io"
24
+
25
+ [tool.setuptools.packages.find]
26
+ where = ["."]
27
+ include = ["memos*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,127 @@
1
+ import pytest
2
+ from unittest.mock import MagicMock, patch
3
+
4
+ from memos import MemosClient, AuthError, RateLimitError, ServerError, MemosError
5
+ from memos.models import Memory, SearchResult, RAGResponse, DreamResult
6
+
7
+ def make_mock_response(status_code: int, json_data: dict):
8
+ mock = MagicMock()
9
+ mock.status_code = status_code
10
+ mock.is_success = 200 <= status_code < 300
11
+ mock.json.return_value = json_data
12
+ mock.text = str(json_data)
13
+ return mock
14
+
15
+ def test_init_requires_api_key():
16
+ with pytest.raises(AuthError):
17
+ MemosClient(api_key="", agent_id="agent_123")
18
+
19
+ def test_init_requires_agent_id():
20
+ with pytest.raises(MemosError):
21
+ MemosClient(api_key="mk0s_key", agent_id="")
22
+
23
+ @patch("httpx.Client.post")
24
+ def test_store_memory_success(mock_post):
25
+ mock_post.return_value = make_mock_response(200, {
26
+ "memory": {
27
+ "id": "mem_1",
28
+ "agent_id": "agent_123",
29
+ "content": "test content",
30
+ "type": "episodic",
31
+ "importance": 3
32
+ }
33
+ })
34
+ client = MemosClient(api_key="key", agent_id="agent")
35
+ result = client.store_memory("test content")
36
+ assert isinstance(result, Memory)
37
+ assert result.content == "test content"
38
+
39
+ @patch("httpx.Client.post")
40
+ def test_store_memory_401(mock_post):
41
+ mock_post.return_value = make_mock_response(401, {"error": "Unauthorized"})
42
+ client = MemosClient(api_key="key", agent_id="agent")
43
+ with pytest.raises(AuthError):
44
+ client.store_memory("test")
45
+
46
+ @patch("httpx.Client.post")
47
+ def test_store_memory_429(mock_post):
48
+ mock_post.return_value = make_mock_response(429, {"error": "Rate limit"})
49
+ client = MemosClient(api_key="key", agent_id="agent")
50
+ with pytest.raises(RateLimitError):
51
+ client.store_memory("test")
52
+
53
+ @patch("httpx.Client.post")
54
+ def test_store_memory_500(mock_post):
55
+ mock_post.return_value = make_mock_response(500, {"error": "Server error"})
56
+ client = MemosClient(api_key="key", agent_id="agent")
57
+ with pytest.raises(ServerError):
58
+ client.store_memory("test")
59
+
60
+ @patch("httpx.Client.get")
61
+ def test_list_memories_returns_list(mock_get):
62
+ mock_get.return_value = make_mock_response(200, {
63
+ "memories": [
64
+ {
65
+ "id": "mem_1",
66
+ "agent_id": "agent",
67
+ "content": "c1",
68
+ "type": "episodic",
69
+ "importance": 3
70
+ }
71
+ ]
72
+ })
73
+ client = MemosClient(api_key="key", agent_id="agent")
74
+ result = client.list_memories()
75
+ assert isinstance(result, list)
76
+ assert all(isinstance(m, Memory) for m in result)
77
+
78
+ @patch("httpx.Client.post")
79
+ def test_search_returns_results(mock_post):
80
+ mock_post.return_value = make_mock_response(200, {
81
+ "results": [
82
+ {
83
+ "id": "mem_1",
84
+ "content": "test result",
85
+ "type": "episodic",
86
+ "importance": 3,
87
+ "score": 0.95
88
+ }
89
+ ]
90
+ })
91
+ client = MemosClient(api_key="key", agent_id="agent")
92
+ result = client.search("test query")
93
+ assert isinstance(result, list)
94
+ assert all(isinstance(r, SearchResult) for r in result)
95
+
96
+ @patch("httpx.Client.post")
97
+ def test_query_returns_rag_response(mock_post):
98
+ mock_post.return_value = make_mock_response(200, {
99
+ "answer": "this is the answer",
100
+ "sources": [],
101
+ "confidence": 0.9
102
+ })
103
+ client = MemosClient(api_key="key", agent_id="agent")
104
+ result = client.query("what do I prefer?")
105
+ assert isinstance(result, RAGResponse)
106
+ assert result.answer == "this is the answer"
107
+
108
+ @patch("httpx.Client.post")
109
+ def test_trigger_dream_returns_dream_result(mock_post):
110
+ mock_post.return_value = make_mock_response(200, {
111
+ "memoriesAnalyzed": 10,
112
+ "patternsFound": 2,
113
+ "newMemoriesCreated": 1,
114
+ "dreamSummary": "summary",
115
+ "newMemories": [],
116
+ "duration": 500
117
+ })
118
+ client = MemosClient(api_key="key", agent_id="agent")
119
+ result = client.trigger_dream()
120
+ assert isinstance(result, DreamResult)
121
+ assert result.memories_analyzed == 10
122
+
123
+ def test_context_manager_closes_client():
124
+ with patch.object(MemosClient, 'close') as mock_close:
125
+ with MemosClient(api_key="key", agent_id="agent"):
126
+ pass
127
+ mock_close.assert_called_once()