cuemap 0.7.2__tar.gz → 0.7.3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cuemap
3
- Version: 0.7.2
3
+ Version: 0.7.3
4
4
  Summary: CueMap Python SDK - High-performance temporal-associative memory
5
5
  Author-email: Kaan Demirel <hello@cuemap.dev>
6
6
  License: MIT
@@ -33,13 +33,26 @@ Requires-Dist: build>=1.0.0; extra == "dev"
33
33
  Requires-Dist: twine>=4.0.0; extra == "dev"
34
34
  Dynamic: license-file
35
35
 
36
- # CueMap Python SDK
36
+ <p align="center">
37
+ <img src="https://cuemap.dev/cuemap-logo.PNG" alt="CueMap" width="120">
38
+ </p>
39
+
40
+ <h1 align="center">CueMap Python SDK</h1>
41
+
42
+ <p align="center">A polished Python client for fast, accurate, and explainable agent memory.</p>
43
+
44
+ <p align="center">
45
+ <a href="https://pypi.org/project/cuemap/"><img src="https://img.shields.io/pypi/v/cuemap?logo=pypi&logoColor=white" alt="PyPI"></a>
46
+ <a href="https://pypi.org/project/cuemap/"><img src="https://img.shields.io/pypi/pyversions/cuemap?logo=python&logoColor=white" alt="Python versions"></a>
47
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-5e5ce6" alt="License"></a>
48
+ <a href="https://github.com/cuemap-dev/cuemap"><img src="https://img.shields.io/badge/engine-v0.7.3-0f766e" alt="Engine compatibility"></a>
49
+ </p>
37
50
 
38
51
  **High-performance temporal-associative memory store** designed for dynamic contextual retrieval.
39
52
 
40
53
  ## Overview
41
54
 
42
- CueMap implements a **Continuous Gradient Algorithm** optimized for associative data structures:
55
+ CueMap uses **temporal-associative retrieval**: lexical and structural candidate generation, with optional semantic reranking. Its main components are:
43
56
 
44
57
  1. **Intersection (Context Filter)**: Triangulates relevant memories by overlapping cues
45
58
  2. **Local semantic reranking**: Uses bundled qint8 MiniLM-L3 by default, or q4 MiniLM-L3 with the edge profile, for bounded semantic ranking inside the engine.
@@ -47,9 +60,9 @@ CueMap implements a **Continuous Gradient Algorithm** optimized for associative
47
60
  4. **Reinforcement (Access-based Learning)**: Frequently accessed memories gain signal strength, remaining highly accessible even as they age.
48
61
  5. **Deterministic Facets & Intent Routing**: Extracts synchronous source, evidence, temporal, type, and entity facets, then uses sparse intent cues and reranking during recall.
49
62
 
50
- As of v0.7.2, CueMap keeps deterministic lexical candidate discovery and adds bundled qint8 `all-MiniLM-L3-v2` for bounded hybrid semantic and intent reranking. The `edge` engine profile uses a q4 build of the same model. No runtime model download is required, and callers can disable the encoder or provide their own vectors.
63
+ As of v0.7.3, CueMap keeps deterministic lexical candidate discovery and adds bundled qint8 `paraphrase-MiniLM-L3-v2` for bounded hybrid semantic and intent reranking. The `edge` engine profile uses a q4 build of the same model. No runtime model download is required, and callers can disable the encoder or provide their own vectors.
51
64
 
52
- v0.7.2 also preserves numeric per-project memory IDs everywhere. If callers need deterministic upsert/dedupe identity, pass `source_key`; memory IDs remain compact runtime addresses.
65
+ v0.7.3 also preserves numeric per-project memory IDs everywhere. If callers need deterministic upsert/dedupe identity, pass `source_key`; memory IDs remain compact runtime addresses.
53
66
 
54
67
  Use this SDK to talk to the Rust engine from Python applications.
55
68
 
@@ -64,7 +77,7 @@ pip install cuemap
64
77
  ### 1. Start the Engine
65
78
 
66
79
  ```bash
67
- docker run -p 8080:8080 cuemap/engine:latest
80
+ docker run -p 8735:8735 cuemap/engine:latest
68
81
  ```
69
82
 
70
83
  ### 2. Basic Usage
@@ -134,9 +147,29 @@ print(response["proof"])
134
147
  # Cryptographic proof of context retrieval
135
148
  ```
136
149
 
137
- ### v0.7.2 Recall Controls
150
+ ### Project memory lifecycle
138
151
 
139
- CueMap v0.7.2 adds local semantic query signals alongside temporal query intent and optional reconstruction passes for longer conversational/codebase context.
152
+ The engine can unload inactive project contexts while keeping their snapshots
153
+ on disk. Normal project operations demand-load a project when needed, so the
154
+ first request after an unload may take longer. Use the explicit helpers when
155
+ you want to control residency:
156
+
157
+ ```python
158
+ client.unload_project("older-repository")
159
+ client.load_project("older-repository")
160
+ client.save_project("older-repository") # persist without unloading
161
+
162
+ for project in client.list_projects():
163
+ print(project["project_id"], project["loaded"])
164
+ ```
165
+
166
+ Portable projects use the same four operations as the CLI:
167
+ `pack_project()`, `load_project_package()`, `push_project()`, and `pull_project()`.
168
+ Use `sync_project(project_id, "s3://bucket/team")` for conflict-safe fast-forward sync.
169
+
170
+ ### v0.7.3 Recall Controls
171
+
172
+ CueMap v0.7.3 adds local semantic query signals alongside temporal query intent and optional reconstruction passes for longer conversational/codebase context.
140
173
 
141
174
  ```python
142
175
  results = client.recall(
@@ -250,3 +283,15 @@ async with AsyncCueMap() as client:
250
283
  ## License
251
284
 
252
285
  MIT
286
+
287
+ ### Recall previews
288
+
289
+ The engine's `POST /recall` accepts `response_mode: "preview"` and optional
290
+ `preview_chars` (100–2000 UTF-16 code units, default 200). Full content remains
291
+ the default. Previews replace each hit's `content` with a leading `preview`,
292
+ `content_truncated`, and `content_length`, preserving metadata and ranking.
293
+ Use previews for broad discovery, then fetch a selected memory with
294
+ `GET /memories/{id}?decoded=true` or read its source. Metadata and diagnostics
295
+ are not capped. TypeScript request objects and Python sync/async `recall`
296
+ accept these same options; Python returns `RecallPreviewResult` for ungrouped
297
+ preview results. The updated engine is required.
@@ -1,10 +1,23 @@
1
- # CueMap Python SDK
1
+ <p align="center">
2
+ <img src="https://cuemap.dev/cuemap-logo.PNG" alt="CueMap" width="120">
3
+ </p>
4
+
5
+ <h1 align="center">CueMap Python SDK</h1>
6
+
7
+ <p align="center">A polished Python client for fast, accurate, and explainable agent memory.</p>
8
+
9
+ <p align="center">
10
+ <a href="https://pypi.org/project/cuemap/"><img src="https://img.shields.io/pypi/v/cuemap?logo=pypi&logoColor=white" alt="PyPI"></a>
11
+ <a href="https://pypi.org/project/cuemap/"><img src="https://img.shields.io/pypi/pyversions/cuemap?logo=python&logoColor=white" alt="Python versions"></a>
12
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-5e5ce6" alt="License"></a>
13
+ <a href="https://github.com/cuemap-dev/cuemap"><img src="https://img.shields.io/badge/engine-v0.7.3-0f766e" alt="Engine compatibility"></a>
14
+ </p>
2
15
 
3
16
  **High-performance temporal-associative memory store** designed for dynamic contextual retrieval.
4
17
 
5
18
  ## Overview
6
19
 
7
- CueMap implements a **Continuous Gradient Algorithm** optimized for associative data structures:
20
+ CueMap uses **temporal-associative retrieval**: lexical and structural candidate generation, with optional semantic reranking. Its main components are:
8
21
 
9
22
  1. **Intersection (Context Filter)**: Triangulates relevant memories by overlapping cues
10
23
  2. **Local semantic reranking**: Uses bundled qint8 MiniLM-L3 by default, or q4 MiniLM-L3 with the edge profile, for bounded semantic ranking inside the engine.
@@ -12,9 +25,9 @@ CueMap implements a **Continuous Gradient Algorithm** optimized for associative
12
25
  4. **Reinforcement (Access-based Learning)**: Frequently accessed memories gain signal strength, remaining highly accessible even as they age.
13
26
  5. **Deterministic Facets & Intent Routing**: Extracts synchronous source, evidence, temporal, type, and entity facets, then uses sparse intent cues and reranking during recall.
14
27
 
15
- As of v0.7.2, CueMap keeps deterministic lexical candidate discovery and adds bundled qint8 `all-MiniLM-L3-v2` for bounded hybrid semantic and intent reranking. The `edge` engine profile uses a q4 build of the same model. No runtime model download is required, and callers can disable the encoder or provide their own vectors.
28
+ As of v0.7.3, CueMap keeps deterministic lexical candidate discovery and adds bundled qint8 `paraphrase-MiniLM-L3-v2` for bounded hybrid semantic and intent reranking. The `edge` engine profile uses a q4 build of the same model. No runtime model download is required, and callers can disable the encoder or provide their own vectors.
16
29
 
17
- v0.7.2 also preserves numeric per-project memory IDs everywhere. If callers need deterministic upsert/dedupe identity, pass `source_key`; memory IDs remain compact runtime addresses.
30
+ v0.7.3 also preserves numeric per-project memory IDs everywhere. If callers need deterministic upsert/dedupe identity, pass `source_key`; memory IDs remain compact runtime addresses.
18
31
 
19
32
  Use this SDK to talk to the Rust engine from Python applications.
20
33
 
@@ -29,7 +42,7 @@ pip install cuemap
29
42
  ### 1. Start the Engine
30
43
 
31
44
  ```bash
32
- docker run -p 8080:8080 cuemap/engine:latest
45
+ docker run -p 8735:8735 cuemap/engine:latest
33
46
  ```
34
47
 
35
48
  ### 2. Basic Usage
@@ -99,9 +112,29 @@ print(response["proof"])
99
112
  # Cryptographic proof of context retrieval
100
113
  ```
101
114
 
102
- ### v0.7.2 Recall Controls
115
+ ### Project memory lifecycle
103
116
 
104
- CueMap v0.7.2 adds local semantic query signals alongside temporal query intent and optional reconstruction passes for longer conversational/codebase context.
117
+ The engine can unload inactive project contexts while keeping their snapshots
118
+ on disk. Normal project operations demand-load a project when needed, so the
119
+ first request after an unload may take longer. Use the explicit helpers when
120
+ you want to control residency:
121
+
122
+ ```python
123
+ client.unload_project("older-repository")
124
+ client.load_project("older-repository")
125
+ client.save_project("older-repository") # persist without unloading
126
+
127
+ for project in client.list_projects():
128
+ print(project["project_id"], project["loaded"])
129
+ ```
130
+
131
+ Portable projects use the same four operations as the CLI:
132
+ `pack_project()`, `load_project_package()`, `push_project()`, and `pull_project()`.
133
+ Use `sync_project(project_id, "s3://bucket/team")` for conflict-safe fast-forward sync.
134
+
135
+ ### v0.7.3 Recall Controls
136
+
137
+ CueMap v0.7.3 adds local semantic query signals alongside temporal query intent and optional reconstruction passes for longer conversational/codebase context.
105
138
 
106
139
  ```python
107
140
  results = client.recall(
@@ -215,3 +248,15 @@ async with AsyncCueMap() as client:
215
248
  ## License
216
249
 
217
250
  MIT
251
+
252
+ ### Recall previews
253
+
254
+ The engine's `POST /recall` accepts `response_mode: "preview"` and optional
255
+ `preview_chars` (100–2000 UTF-16 code units, default 200). Full content remains
256
+ the default. Previews replace each hit's `content` with a leading `preview`,
257
+ `content_truncated`, and `content_length`, preserving metadata and ranking.
258
+ Use previews for broad discovery, then fetch a selected memory with
259
+ `GET /memories/{id}?decoded=true` or read its source. Metadata and diagnostics
260
+ are not capped. TypeScript request objects and Python sync/async `recall`
261
+ accept these same options; Python returns `RecallPreviewResult` for ungrouped
262
+ preview results. The updated engine is required.
@@ -19,11 +19,11 @@ Example:
19
19
 
20
20
  from .client import CueMap, AsyncCueMap
21
21
  from .embedded import EmbeddedCueMap, resolve_cuemap_binary
22
- from .models import Memory, RecallResult
22
+ from .models import Memory, RecallResult, RecallPreviewResult
23
23
  from .exceptions import CueMapError, ConnectionError, AuthenticationError
24
24
  from .grounding import CueMapGroundingRetriever, AsyncCueMapGroundingRetriever
25
25
 
26
- __version__ = "0.7.2"
26
+ __version__ = "0.7.3"
27
27
  __all__ = [
28
28
  "CueMap",
29
29
  "AsyncCueMap",
@@ -31,6 +31,7 @@ __all__ = [
31
31
  "resolve_cuemap_binary",
32
32
  "Memory",
33
33
  "RecallResult",
34
+ "RecallPreviewResult",
34
35
  "CueMapError",
35
36
  "ConnectionError",
36
37
  "AuthenticationError",
@@ -3,7 +3,7 @@
3
3
  import httpx
4
4
  from typing import List, Optional, Dict, Any
5
5
 
6
- from .models import Memory, RecallResult
6
+ from .models import Memory, RecallResult, RecallPreviewResult
7
7
  from .exceptions import CueMapError, ConnectionError, AuthenticationError
8
8
 
9
9
 
@@ -27,7 +27,7 @@ class CueMap:
27
27
 
28
28
  def __init__(
29
29
  self,
30
- url: str = "http://localhost:8080",
30
+ url: str = "http://localhost:8735",
31
31
  api_key: Optional[str] = None,
32
32
  project_id: Optional[str] = None,
33
33
  timeout: float = 30.0
@@ -185,6 +185,8 @@ class CueMap:
185
185
  disable_systems_consolidation: Optional[bool] = None,
186
186
  semantic_mode: str = "hybrid",
187
187
  query_embedding: Optional[List[float]] = None,
188
+ response_mode: str = "full",
189
+ preview_chars: int = 200,
188
190
  ) -> List[RecallResult]:
189
191
  """
190
192
  Recall memories by cues or natural language.
@@ -231,6 +233,8 @@ class CueMap:
231
233
  "cuebridge_gap_limit": cuebridge_gap_limit,
232
234
  "semantic_mode": semantic_mode,
233
235
  "query_embedding": query_embedding,
236
+ "response_mode": response_mode,
237
+ "preview_chars": preview_chars,
234
238
  }
235
239
  if cues:
236
240
  payload["cues"] = cues
@@ -257,7 +261,7 @@ class CueMap:
257
261
  if projects and isinstance(results, list) and len(results) > 0 and "project_id" in results[0]:
258
262
  return data
259
263
 
260
- return [RecallResult(**r) for r in results]
264
+ return [(RecallPreviewResult if response_mode == "preview" else RecallResult)(**r) for r in results]
261
265
 
262
266
  def recall_grounded(
263
267
  self,
@@ -302,8 +306,12 @@ class CueMap:
302
306
 
303
307
  return response.json()
304
308
 
305
- def list_projects(self) -> List[str]:
306
- """List all projects (multi-tenant only)."""
309
+ def list_projects(self) -> List[Dict[str, Any]]:
310
+ """List all projects and their runtime state (multi-tenant only).
311
+
312
+ Each item includes ``loaded`` so callers can distinguish a project
313
+ whose snapshot exists on disk from one currently resident in RAM.
314
+ """
307
315
  response = self.client.get(
308
316
  "/projects",
309
317
  headers=self._headers()
@@ -312,6 +320,93 @@ class CueMap:
312
320
  raise CueMapError(f"Failed to list projects: {response.text}")
313
321
  return response.json()
314
322
 
323
+ def load_project(self, project_id: str) -> Dict[str, Any]:
324
+ """Load a project's snapshot into engine memory."""
325
+ response = self.client.post(
326
+ f"/projects/{project_id}/load",
327
+ headers=self._headers(),
328
+ )
329
+ if response.status_code != 200:
330
+ raise CueMapError(f"Failed to load project: {response.text}")
331
+ return response.json()
332
+
333
+ def save_project(self, project_id: str) -> Dict[str, Any]:
334
+ """Persist a current project snapshot without unloading it."""
335
+ response = self.client.post(
336
+ f"/projects/{project_id}/save",
337
+ headers=self._headers(),
338
+ )
339
+ if response.status_code != 200:
340
+ raise CueMapError(f"Failed to save project: {response.text}")
341
+ return response.json()
342
+
343
+ def unload_project(self, project_id: str) -> Dict[str, Any]:
344
+ """Persist and unload a project from engine memory."""
345
+ response = self.client.post(
346
+ f"/projects/{project_id}/unload",
347
+ headers=self._headers(),
348
+ )
349
+ if response.status_code != 200:
350
+ raise CueMapError(f"Failed to unload project: {response.text}")
351
+ return response.json()
352
+
353
+ def pack_project(self, project_id: str) -> bytes:
354
+ """Return a ready-to-query project as portable ``.cuemap`` bytes."""
355
+ response = self.client.post(
356
+ f"/projects/{project_id}/pack",
357
+ headers=self._headers(),
358
+ )
359
+ if response.status_code != 200:
360
+ raise CueMapError(f"Failed to pack project: {response.text}")
361
+ return response.content
362
+
363
+ def load_project_package(self, package: bytes) -> Dict[str, Any]:
364
+ """Install and warm a portable ``.cuemap`` package."""
365
+ response = self.client.post(
366
+ "/projects/load",
367
+ content=package,
368
+ headers={
369
+ **self._headers(),
370
+ "Content-Type": "application/vnd.cuemap.project",
371
+ },
372
+ )
373
+ if response.status_code != 200:
374
+ raise CueMapError(f"Failed to load project package: {response.text}")
375
+ return response.json()
376
+
377
+ def push_project(self, project_id: str, destination: str) -> Dict[str, Any]:
378
+ """Pack and upload a project using the server's configured AWS CLI."""
379
+ response = self.client.post(
380
+ f"/projects/{project_id}/push",
381
+ json={"destination": destination},
382
+ headers=self._headers(),
383
+ )
384
+ if response.status_code != 200:
385
+ raise CueMapError(f"Failed to push project: {response.text}")
386
+ return response.json()
387
+
388
+ def pull_project(self, source: str) -> Dict[str, Any]:
389
+ """Download, install, and warm a project using the server's AWS CLI."""
390
+ response = self.client.post(
391
+ "/projects/pull",
392
+ json={"source": source},
393
+ headers=self._headers(),
394
+ )
395
+ if response.status_code != 200:
396
+ raise CueMapError(f"Failed to pull project: {response.text}")
397
+ return response.json()
398
+
399
+ def sync_project(self, project_id: str, remote: str) -> Dict[str, Any]:
400
+ """Fast-forward a project through its immutable S3 sync history."""
401
+ response = self.client.post(
402
+ f"/projects/{project_id}/sync",
403
+ json={"remote": remote},
404
+ headers=self._headers(),
405
+ )
406
+ if response.status_code != 200:
407
+ raise CueMapError(f"Failed to sync project: {response.text}")
408
+ return response.json()
409
+
315
410
  def create_project(self, project_id: str) -> Dict[str, Any]:
316
411
  """Create a project."""
317
412
  response = self.client.post(
@@ -758,7 +853,7 @@ class AsyncCueMap:
758
853
 
759
854
  def __init__(
760
855
  self,
761
- url: str = "http://localhost:8080",
856
+ url: str = "http://localhost:8735",
762
857
  api_key: Optional[str] = None,
763
858
  project_id: Optional[str] = None,
764
859
  timeout: float = 30.0
@@ -889,6 +984,8 @@ class AsyncCueMap:
889
984
  disable_systems_consolidation: Optional[bool] = None,
890
985
  semantic_mode: str = "hybrid",
891
986
  query_embedding: Optional[List[float]] = None,
987
+ response_mode: str = "full",
988
+ preview_chars: int = 200,
892
989
  ) -> List[RecallResult]:
893
990
  """Recall memories (async)."""
894
991
  payload = {
@@ -915,6 +1012,8 @@ class AsyncCueMap:
915
1012
  "cuebridge_gap_limit": cuebridge_gap_limit,
916
1013
  "semantic_mode": semantic_mode,
917
1014
  "query_embedding": query_embedding,
1015
+ "response_mode": response_mode,
1016
+ "preview_chars": preview_chars,
918
1017
  }
919
1018
  if cues:
920
1019
  payload["cues"] = cues
@@ -941,7 +1040,7 @@ class AsyncCueMap:
941
1040
  if projects and isinstance(results, list) and len(results) > 0 and "project_id" in results[0]:
942
1041
  return data
943
1042
 
944
- return [RecallResult(**r) for r in results]
1043
+ return [(RecallPreviewResult if response_mode == "preview" else RecallResult)(**r) for r in results]
945
1044
 
946
1045
  async def recall_grounded(
947
1046
  self,
@@ -979,8 +1078,8 @@ class AsyncCueMap:
979
1078
 
980
1079
  return response.json()
981
1080
 
982
- async def list_projects(self) -> List[str]:
983
- """List all projects (async, multi-tenant only)."""
1081
+ async def list_projects(self) -> List[Dict[str, Any]]:
1082
+ """List all projects and their runtime state (async, multi-tenant only)."""
984
1083
  response = await self.client.get(
985
1084
  "/projects",
986
1085
  headers=self._headers()
@@ -989,6 +1088,93 @@ class AsyncCueMap:
989
1088
  raise CueMapError(f"Failed to list projects: {response.text}")
990
1089
  return response.json()
991
1090
 
1091
+ async def load_project(self, project_id: str) -> Dict[str, Any]:
1092
+ """Load a project's snapshot into engine memory (async)."""
1093
+ response = await self.client.post(
1094
+ f"/projects/{project_id}/load",
1095
+ headers=self._headers(),
1096
+ )
1097
+ if response.status_code != 200:
1098
+ raise CueMapError(f"Failed to load project: {response.text}")
1099
+ return response.json()
1100
+
1101
+ async def save_project(self, project_id: str) -> Dict[str, Any]:
1102
+ """Persist a current project snapshot without unloading it (async)."""
1103
+ response = await self.client.post(
1104
+ f"/projects/{project_id}/save",
1105
+ headers=self._headers(),
1106
+ )
1107
+ if response.status_code != 200:
1108
+ raise CueMapError(f"Failed to save project: {response.text}")
1109
+ return response.json()
1110
+
1111
+ async def unload_project(self, project_id: str) -> Dict[str, Any]:
1112
+ """Persist and unload a project from engine memory (async)."""
1113
+ response = await self.client.post(
1114
+ f"/projects/{project_id}/unload",
1115
+ headers=self._headers(),
1116
+ )
1117
+ if response.status_code != 200:
1118
+ raise CueMapError(f"Failed to unload project: {response.text}")
1119
+ return response.json()
1120
+
1121
+ async def pack_project(self, project_id: str) -> bytes:
1122
+ """Return a ready-to-query project as portable ``.cuemap`` bytes (async)."""
1123
+ response = await self.client.post(
1124
+ f"/projects/{project_id}/pack",
1125
+ headers=self._headers(),
1126
+ )
1127
+ if response.status_code != 200:
1128
+ raise CueMapError(f"Failed to pack project: {response.text}")
1129
+ return response.content
1130
+
1131
+ async def load_project_package(self, package: bytes) -> Dict[str, Any]:
1132
+ """Install and warm a portable ``.cuemap`` package (async)."""
1133
+ response = await self.client.post(
1134
+ "/projects/load",
1135
+ content=package,
1136
+ headers={
1137
+ **self._headers(),
1138
+ "Content-Type": "application/vnd.cuemap.project",
1139
+ },
1140
+ )
1141
+ if response.status_code != 200:
1142
+ raise CueMapError(f"Failed to load project package: {response.text}")
1143
+ return response.json()
1144
+
1145
+ async def push_project(self, project_id: str, destination: str) -> Dict[str, Any]:
1146
+ """Pack and upload a project using the server's configured AWS CLI (async)."""
1147
+ response = await self.client.post(
1148
+ f"/projects/{project_id}/push",
1149
+ json={"destination": destination},
1150
+ headers=self._headers(),
1151
+ )
1152
+ if response.status_code != 200:
1153
+ raise CueMapError(f"Failed to push project: {response.text}")
1154
+ return response.json()
1155
+
1156
+ async def pull_project(self, source: str) -> Dict[str, Any]:
1157
+ """Download, install, and warm a project using the server's AWS CLI (async)."""
1158
+ response = await self.client.post(
1159
+ "/projects/pull",
1160
+ json={"source": source},
1161
+ headers=self._headers(),
1162
+ )
1163
+ if response.status_code != 200:
1164
+ raise CueMapError(f"Failed to pull project: {response.text}")
1165
+ return response.json()
1166
+
1167
+ async def sync_project(self, project_id: str, remote: str) -> Dict[str, Any]:
1168
+ """Fast-forward a project through its immutable S3 sync history (async)."""
1169
+ response = await self.client.post(
1170
+ f"/projects/{project_id}/sync",
1171
+ json={"remote": remote},
1172
+ headers=self._headers(),
1173
+ )
1174
+ if response.status_code != 200:
1175
+ raise CueMapError(f"Failed to sync project: {response.text}")
1176
+ return response.json()
1177
+
992
1178
  async def create_project(self, project_id: str) -> Dict[str, Any]:
993
1179
  """Create a project (async)."""
994
1180
  response = await self.client.post(
@@ -39,9 +39,11 @@ def _free_port() -> int:
39
39
 
40
40
 
41
41
  def _platform_package() -> str:
42
- operating_system = platform.system().lower()
42
+ operating_system = {"Windows": "win32", "Darwin": "darwin", "Linux": "linux"}.get(platform.system())
43
43
  machine = platform.machine().lower()
44
- architecture = "arm64" if machine in {"arm64", "aarch64"} else "x64"
44
+ architecture = {"arm64": "arm64", "aarch64": "arm64", "amd64": "x64", "x86_64": "x64"}.get(machine)
45
+ if operating_system is None or architecture is None or (operating_system == "win32" and architecture != "x64"):
46
+ raise RuntimeError(f"Unsupported CueMap platform: {platform.system()} {machine}")
45
47
  return f"@cuemap-dev/engine-{operating_system}-{architecture}"
46
48
 
47
49
 
@@ -49,9 +51,16 @@ def _npm_global_binary() -> Optional[str]:
49
51
  npm = shutil.which("npm")
50
52
  if not npm:
51
53
  return None
54
+ npm_command = [npm]
55
+ if platform.system() == "Windows":
56
+ node = shutil.which("node")
57
+ npm_cli = Path(npm).parent / "node_modules" / "npm" / "bin" / "npm-cli.js"
58
+ if not node or not npm_cli.is_file():
59
+ return None
60
+ npm_command = [node, str(npm_cli)]
52
61
  try:
53
62
  root = subprocess.run(
54
- [npm, "root", "--global"],
63
+ [*npm_command, "root", "--global"],
55
64
  check=True,
56
65
  capture_output=True,
57
66
  text=True,
@@ -59,8 +68,8 @@ def _npm_global_binary() -> Optional[str]:
59
68
  ).stdout.strip()
60
69
  except (OSError, subprocess.SubprocessError):
61
70
  return None
62
- binary_name = "cuemap.exe" if os.name == "nt" else "cuemap"
63
- candidate = Path(root) / _platform_package() / "bin" / binary_name
71
+ package_bin = Path(root) / _platform_package() / "bin"
72
+ candidate = package_bin / ("cuemap-native.exe" if platform.system() == "Windows" else "cuemap")
64
73
  return str(candidate) if candidate.is_file() else None
65
74
 
66
75
 
@@ -75,7 +84,7 @@ def resolve_cuemap_binary(explicit_path: Optional[str] = None) -> str:
75
84
  return str(path)
76
85
 
77
86
  installed = shutil.which("cuemap")
78
- if installed:
87
+ if installed and Path(installed).suffix.lower() not in {".cmd", ".bat", ".ps1"}:
79
88
  return installed
80
89
 
81
90
  global_binary = _npm_global_binary()
@@ -113,7 +122,7 @@ class EmbeddedCueMap:
113
122
  *,
114
123
  url: Optional[str] = None,
115
124
  bin_path: Optional[str] = None,
116
- port: int = 8080,
125
+ port: int = 8735,
117
126
  config_path: Optional[str] = None,
118
127
  api_key: Optional[str] = None,
119
128
  startup_timeout: float = 15.0,
@@ -138,13 +147,27 @@ class EmbeddedCueMap:
138
147
  selected_port = _free_port() if status == "occupied" else port
139
148
  selected_url = f"http://127.0.0.1:{selected_port}"
140
149
  executable = resolve_cuemap_binary(bin_path)
141
- arguments = [executable, "start", "--port", str(selected_port)]
150
+ command = [executable]
151
+ if platform.system() == "Windows" and Path(executable).suffix.lower() != ".exe":
152
+ if Path(executable).suffix.lower() in {".cmd", ".bat", ".ps1"}:
153
+ raise ValueError("Use the native .exe or the npm package bin/cuemap wrapper, not a shell shim")
154
+ node = shutil.which("node")
155
+ if not node:
156
+ raise FileNotFoundError("Node.js is required to launch the CueMap npm wrapper")
157
+ command = [node, executable]
158
+ arguments = [*command, "start", "--port", str(selected_port)]
142
159
  if config_path:
143
160
  arguments.extend(["--config", str(Path(config_path).expanduser())])
144
161
  process_env = dict(os.environ)
145
162
  if env:
146
163
  process_env.update(env)
147
164
  process_env["CUEMAP_PORT"] = str(selected_port)
165
+ process_env["CUEMAP_HOST"] = "127.0.0.1"
166
+ tokenizer = Path(executable).parent.parent / "assets" / "en_tokenizer.bin"
167
+ if Path(executable).name == "cuemap-native.exe" and tokenizer.is_file():
168
+ process_env.setdefault("TOKENIZER_PATH", str(tokenizer))
169
+ if api_key:
170
+ process_env["CUEMAP_API_KEY"] = api_key
148
171
 
149
172
  log(f"Starting CueMap at {selected_url}")
150
173
  process = subprocess.Popen(
@@ -164,7 +187,7 @@ class EmbeddedCueMap:
164
187
  return cls(selected_url, True, process, shutdown_timeout)
165
188
  time.sleep(0.1)
166
189
 
167
- process.terminate()
190
+ cls(selected_url, True, process, shutdown_timeout).stop()
168
191
  raise TimeoutError(f"CueMap did not become ready within {startup_timeout:g}s")
169
192
 
170
193
  def stop(self) -> None:
@@ -37,3 +37,11 @@ class RecallResult(BaseModel):
37
37
  structural_cues: List[str] = Field(default_factory=list)
38
38
  metadata: Dict[str, Any] = Field(default_factory=dict)
39
39
  explain: Optional[Dict[str, Any]] = None
40
+
41
+
42
+ class RecallPreviewResult(RecallResult):
43
+ """Engine-provided leading excerpt; fetch the memory for full evidence."""
44
+ content: Optional[str] = None
45
+ preview: str
46
+ content_truncated: bool
47
+ content_length: int
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cuemap
3
- Version: 0.7.2
3
+ Version: 0.7.3
4
4
  Summary: CueMap Python SDK - High-performance temporal-associative memory
5
5
  Author-email: Kaan Demirel <hello@cuemap.dev>
6
6
  License: MIT
@@ -33,13 +33,26 @@ Requires-Dist: build>=1.0.0; extra == "dev"
33
33
  Requires-Dist: twine>=4.0.0; extra == "dev"
34
34
  Dynamic: license-file
35
35
 
36
- # CueMap Python SDK
36
+ <p align="center">
37
+ <img src="https://cuemap.dev/cuemap-logo.PNG" alt="CueMap" width="120">
38
+ </p>
39
+
40
+ <h1 align="center">CueMap Python SDK</h1>
41
+
42
+ <p align="center">A polished Python client for fast, accurate, and explainable agent memory.</p>
43
+
44
+ <p align="center">
45
+ <a href="https://pypi.org/project/cuemap/"><img src="https://img.shields.io/pypi/v/cuemap?logo=pypi&logoColor=white" alt="PyPI"></a>
46
+ <a href="https://pypi.org/project/cuemap/"><img src="https://img.shields.io/pypi/pyversions/cuemap?logo=python&logoColor=white" alt="Python versions"></a>
47
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-5e5ce6" alt="License"></a>
48
+ <a href="https://github.com/cuemap-dev/cuemap"><img src="https://img.shields.io/badge/engine-v0.7.3-0f766e" alt="Engine compatibility"></a>
49
+ </p>
37
50
 
38
51
  **High-performance temporal-associative memory store** designed for dynamic contextual retrieval.
39
52
 
40
53
  ## Overview
41
54
 
42
- CueMap implements a **Continuous Gradient Algorithm** optimized for associative data structures:
55
+ CueMap uses **temporal-associative retrieval**: lexical and structural candidate generation, with optional semantic reranking. Its main components are:
43
56
 
44
57
  1. **Intersection (Context Filter)**: Triangulates relevant memories by overlapping cues
45
58
  2. **Local semantic reranking**: Uses bundled qint8 MiniLM-L3 by default, or q4 MiniLM-L3 with the edge profile, for bounded semantic ranking inside the engine.
@@ -47,9 +60,9 @@ CueMap implements a **Continuous Gradient Algorithm** optimized for associative
47
60
  4. **Reinforcement (Access-based Learning)**: Frequently accessed memories gain signal strength, remaining highly accessible even as they age.
48
61
  5. **Deterministic Facets & Intent Routing**: Extracts synchronous source, evidence, temporal, type, and entity facets, then uses sparse intent cues and reranking during recall.
49
62
 
50
- As of v0.7.2, CueMap keeps deterministic lexical candidate discovery and adds bundled qint8 `all-MiniLM-L3-v2` for bounded hybrid semantic and intent reranking. The `edge` engine profile uses a q4 build of the same model. No runtime model download is required, and callers can disable the encoder or provide their own vectors.
63
+ As of v0.7.3, CueMap keeps deterministic lexical candidate discovery and adds bundled qint8 `paraphrase-MiniLM-L3-v2` for bounded hybrid semantic and intent reranking. The `edge` engine profile uses a q4 build of the same model. No runtime model download is required, and callers can disable the encoder or provide their own vectors.
51
64
 
52
- v0.7.2 also preserves numeric per-project memory IDs everywhere. If callers need deterministic upsert/dedupe identity, pass `source_key`; memory IDs remain compact runtime addresses.
65
+ v0.7.3 also preserves numeric per-project memory IDs everywhere. If callers need deterministic upsert/dedupe identity, pass `source_key`; memory IDs remain compact runtime addresses.
53
66
 
54
67
  Use this SDK to talk to the Rust engine from Python applications.
55
68
 
@@ -64,7 +77,7 @@ pip install cuemap
64
77
  ### 1. Start the Engine
65
78
 
66
79
  ```bash
67
- docker run -p 8080:8080 cuemap/engine:latest
80
+ docker run -p 8735:8735 cuemap/engine:latest
68
81
  ```
69
82
 
70
83
  ### 2. Basic Usage
@@ -134,9 +147,29 @@ print(response["proof"])
134
147
  # Cryptographic proof of context retrieval
135
148
  ```
136
149
 
137
- ### v0.7.2 Recall Controls
150
+ ### Project memory lifecycle
138
151
 
139
- CueMap v0.7.2 adds local semantic query signals alongside temporal query intent and optional reconstruction passes for longer conversational/codebase context.
152
+ The engine can unload inactive project contexts while keeping their snapshots
153
+ on disk. Normal project operations demand-load a project when needed, so the
154
+ first request after an unload may take longer. Use the explicit helpers when
155
+ you want to control residency:
156
+
157
+ ```python
158
+ client.unload_project("older-repository")
159
+ client.load_project("older-repository")
160
+ client.save_project("older-repository") # persist without unloading
161
+
162
+ for project in client.list_projects():
163
+ print(project["project_id"], project["loaded"])
164
+ ```
165
+
166
+ Portable projects use the same four operations as the CLI:
167
+ `pack_project()`, `load_project_package()`, `push_project()`, and `pull_project()`.
168
+ Use `sync_project(project_id, "s3://bucket/team")` for conflict-safe fast-forward sync.
169
+
170
+ ### v0.7.3 Recall Controls
171
+
172
+ CueMap v0.7.3 adds local semantic query signals alongside temporal query intent and optional reconstruction passes for longer conversational/codebase context.
140
173
 
141
174
  ```python
142
175
  results = client.recall(
@@ -250,3 +283,15 @@ async with AsyncCueMap() as client:
250
283
  ## License
251
284
 
252
285
  MIT
286
+
287
+ ### Recall previews
288
+
289
+ The engine's `POST /recall` accepts `response_mode: "preview"` and optional
290
+ `preview_chars` (100–2000 UTF-16 code units, default 200). Full content remains
291
+ the default. Previews replace each hit's `content` with a leading `preview`,
292
+ `content_truncated`, and `content_length`, preserving metadata and ranking.
293
+ Use previews for broad discovery, then fetch a selected memory with
294
+ `GET /memories/{id}?decoded=true` or read its source. Metadata and diagnostics
295
+ are not capped. TypeScript request objects and Python sync/async `recall`
296
+ accept these same options; Python returns `RecallPreviewResult` for ungrouped
297
+ preview results. The updated engine is required.
@@ -1,10 +1,10 @@
1
1
  [build-system]
2
- requires = ["setuptools>=61.0"]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
3
  build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cuemap"
7
- version = "0.7.2"
7
+ version = "0.7.3"
8
8
  description = "CueMap Python SDK - High-performance temporal-associative memory"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes