hindsight-api 0.3.0__py3-none-any.whl → 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. hindsight_api/admin/cli.py +59 -0
  2. hindsight_api/alembic/versions/h3c4d5e6f7g8_mental_models_v4.py +112 -0
  3. hindsight_api/alembic/versions/i4d5e6f7g8h9_delete_opinions.py +41 -0
  4. hindsight_api/alembic/versions/j5e6f7g8h9i0_mental_model_versions.py +95 -0
  5. hindsight_api/alembic/versions/k6f7g8h9i0j1_add_directive_subtype.py +58 -0
  6. hindsight_api/alembic/versions/l7g8h9i0j1k2_add_worker_columns.py +109 -0
  7. hindsight_api/alembic/versions/m8h9i0j1k2l3_mental_model_id_to_text.py +41 -0
  8. hindsight_api/alembic/versions/n9i0j1k2l3m4_learnings_and_pinned_reflections.py +134 -0
  9. hindsight_api/alembic/versions/o0j1k2l3m4n5_migrate_mental_models_data.py +113 -0
  10. hindsight_api/alembic/versions/p1k2l3m4n5o6_new_knowledge_architecture.py +194 -0
  11. hindsight_api/alembic/versions/q2l3m4n5o6p7_fix_mental_model_fact_type.py +50 -0
  12. hindsight_api/alembic/versions/r3m4n5o6p7q8_add_reflect_response_to_reflections.py +47 -0
  13. hindsight_api/alembic/versions/s4n5o6p7q8r9_add_consolidated_at_to_memory_units.py +53 -0
  14. hindsight_api/alembic/versions/t5o6p7q8r9s0_rename_mental_models_to_observations.py +134 -0
  15. hindsight_api/alembic/versions/u6p7q8r9s0t1_mental_models_text_id.py +41 -0
  16. hindsight_api/alembic/versions/v7q8r9s0t1u2_add_max_tokens_to_mental_models.py +50 -0
  17. hindsight_api/api/http.py +1119 -93
  18. hindsight_api/api/mcp.py +11 -191
  19. hindsight_api/config.py +145 -45
  20. hindsight_api/engine/consolidation/__init__.py +5 -0
  21. hindsight_api/engine/consolidation/consolidator.py +859 -0
  22. hindsight_api/engine/consolidation/prompts.py +69 -0
  23. hindsight_api/engine/cross_encoder.py +114 -9
  24. hindsight_api/engine/directives/__init__.py +5 -0
  25. hindsight_api/engine/directives/models.py +37 -0
  26. hindsight_api/engine/embeddings.py +102 -5
  27. hindsight_api/engine/interface.py +32 -13
  28. hindsight_api/engine/llm_wrapper.py +505 -43
  29. hindsight_api/engine/memory_engine.py +2090 -1089
  30. hindsight_api/engine/mental_models/__init__.py +14 -0
  31. hindsight_api/engine/mental_models/models.py +53 -0
  32. hindsight_api/engine/reflect/__init__.py +18 -0
  33. hindsight_api/engine/reflect/agent.py +933 -0
  34. hindsight_api/engine/reflect/models.py +109 -0
  35. hindsight_api/engine/reflect/observations.py +186 -0
  36. hindsight_api/engine/reflect/prompts.py +483 -0
  37. hindsight_api/engine/reflect/tools.py +437 -0
  38. hindsight_api/engine/reflect/tools_schema.py +250 -0
  39. hindsight_api/engine/response_models.py +130 -4
  40. hindsight_api/engine/retain/bank_utils.py +79 -201
  41. hindsight_api/engine/retain/fact_extraction.py +81 -48
  42. hindsight_api/engine/retain/fact_storage.py +5 -8
  43. hindsight_api/engine/retain/link_utils.py +5 -8
  44. hindsight_api/engine/retain/orchestrator.py +1 -55
  45. hindsight_api/engine/retain/types.py +2 -2
  46. hindsight_api/engine/search/graph_retrieval.py +2 -2
  47. hindsight_api/engine/search/link_expansion_retrieval.py +164 -29
  48. hindsight_api/engine/search/mpfp_retrieval.py +1 -1
  49. hindsight_api/engine/search/retrieval.py +14 -14
  50. hindsight_api/engine/search/think_utils.py +41 -140
  51. hindsight_api/engine/search/trace.py +0 -1
  52. hindsight_api/engine/search/tracer.py +2 -5
  53. hindsight_api/engine/search/types.py +0 -3
  54. hindsight_api/engine/task_backend.py +112 -196
  55. hindsight_api/engine/utils.py +0 -151
  56. hindsight_api/extensions/__init__.py +10 -1
  57. hindsight_api/extensions/builtin/tenant.py +5 -1
  58. hindsight_api/extensions/operation_validator.py +81 -4
  59. hindsight_api/extensions/tenant.py +26 -0
  60. hindsight_api/main.py +16 -5
  61. hindsight_api/mcp_local.py +12 -53
  62. hindsight_api/mcp_tools.py +494 -0
  63. hindsight_api/models.py +0 -2
  64. hindsight_api/worker/__init__.py +11 -0
  65. hindsight_api/worker/main.py +296 -0
  66. hindsight_api/worker/poller.py +486 -0
  67. {hindsight_api-0.3.0.dist-info → hindsight_api-0.4.0.dist-info}/METADATA +12 -6
  68. hindsight_api-0.4.0.dist-info/RECORD +112 -0
  69. {hindsight_api-0.3.0.dist-info → hindsight_api-0.4.0.dist-info}/entry_points.txt +1 -0
  70. hindsight_api/engine/retain/observation_regeneration.py +0 -254
  71. hindsight_api/engine/search/observation_utils.py +0 -125
  72. hindsight_api/engine/search/scoring.py +0 -159
  73. hindsight_api-0.3.0.dist-info/RECORD +0 -82
  74. {hindsight_api-0.3.0.dist-info → hindsight_api-0.4.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,69 @@
1
+ """Prompts for the consolidation engine."""
2
+
3
+ CONSOLIDATION_SYSTEM_PROMPT = """You are a memory consolidation system. Your job is to convert facts into durable knowledge (observations) and merge with existing knowledge when appropriate.
4
+
5
+ You must output ONLY valid JSON with no markdown formatting, no code blocks, and no additional text.
6
+
7
+ ## EXTRACT DURABLE KNOWLEDGE, NOT EPHEMERAL STATE
8
+ Facts often describe events or actions. Extract the DURABLE KNOWLEDGE implied by the fact, not the transient state.
9
+
10
+ Examples of extracting durable knowledge:
11
+ - "User moved to Room 203" -> "Room 203 exists" (location exists, not where user is now)
12
+ - "User visited Acme Corp at Room 105" -> "Acme Corp is located in Room 105"
13
+ - "User took the elevator to floor 3" -> "Floor 3 is accessible by elevator"
14
+ - "User met Sarah at the lobby" -> "Sarah can be found at the lobby"
15
+
16
+ DO NOT track current user position/state as knowledge - that changes constantly.
17
+ DO track permanent facts learned from the user's actions.
18
+
19
+ ## PRESERVE SPECIFIC DETAILS
20
+ Keep names, locations, numbers, and other specifics. Do NOT:
21
+ - Abstract into general principles
22
+ - Generate business insights
23
+ - Make knowledge generic
24
+
25
+ GOOD examples:
26
+ - Fact: "John likes pizza" -> "John likes pizza"
27
+ - Fact: "Alice works at Google" -> "Alice works at Google"
28
+
29
+ BAD examples:
30
+ - "John likes pizza" -> "Understanding dietary preferences helps..." (TOO ABSTRACT)
31
+ - "User is at Room 203" -> "User is currently at Room 203" (EPHEMERAL STATE)
32
+
33
+ ## MERGE RULES (when comparing to existing observations):
34
+ 1. REDUNDANT: Same information worded differently → update existing
35
+ 2. CONTRADICTION: Opposite information about same topic → update with history (e.g., "used to X, now Y")
36
+ 3. UPDATE: New state replacing old state → update with history
37
+
38
+ ## CRITICAL RULES:
39
+ - NEVER merge facts about DIFFERENT people
40
+ - NEVER merge unrelated topics (food preferences vs work vs hobbies)
41
+ - When merging contradictions, capture the CHANGE (before → after)
42
+ - Keep observations focused on ONE specific topic per person
43
+ - The "text" field MUST contain durable knowledge, not ephemeral state
44
+ - Do NOT include "tags" in output - tags are handled automatically"""
45
+
46
+ CONSOLIDATION_USER_PROMPT = """Analyze this new fact and consolidate into knowledge.
47
+ {mission_section}
48
+ NEW FACT: {fact_text}
49
+
50
+ EXISTING OBSERVATIONS:
51
+ {observations_text}
52
+
53
+ Instructions:
54
+ 1. First, extract the DURABLE KNOWLEDGE from the fact (not ephemeral state like "user is at X")
55
+ 2. Then compare with existing observations:
56
+ - If an observation covers the same topic: UPDATE it with the new knowledge
57
+ - If no observation covers the topic: CREATE a new one
58
+
59
+ Output JSON array of actions (ALWAYS an array, even for single action):
60
+ [
61
+ {{"action": "update", "learning_id": "uuid", "text": "updated durable knowledge", "reason": "..."}},
62
+ {{"action": "create", "text": "new durable knowledge", "reason": "..."}}
63
+ ]
64
+
65
+ If NO consolidation is needed (fact is purely ephemeral with no durable knowledge):
66
+ []
67
+
68
+ If no observations exist and fact contains durable knowledge:
69
+ [{{"action": "create", "text": "durable knowledge text", "reason": "new topic"}}]"""
@@ -130,13 +130,28 @@ class LocalSTCrossEncoder(CrossEncoderModel):
130
130
  "Install it with: pip install sentence-transformers"
131
131
  )
132
132
 
133
- # Note: We use CPU even when GPU/MPS is available because:
134
- # 1. The reranker model (MiniLM) is tiny (~22M params)
135
- # 2. Batch sizes are small (~100-200 pairs)
136
- # 3. Data transfer overhead to GPU outweighs compute benefit
137
- # 4. CPU inference is actually faster for this workload
138
133
  logger.info(f"Reranker: initializing local provider with model {self.model_name}")
139
- self._model = CrossEncoder(self.model_name)
134
+
135
+ # Determine device based on hardware availability.
136
+ # We always set low_cpu_mem_usage=False to prevent lazy loading (meta tensors)
137
+ # which can cause issues when accelerate is installed but no GPU is available.
138
+ # Note: We do NOT use device_map because CrossEncoder internally calls .to(device)
139
+ # after loading, which conflicts with accelerate's device_map handling.
140
+ import torch
141
+
142
+ # Check for GPU (CUDA) or Apple Silicon (MPS)
143
+ has_gpu = torch.cuda.is_available() or (hasattr(torch.backends, "mps") and torch.backends.mps.is_available())
144
+
145
+ if has_gpu:
146
+ device = None # Let sentence-transformers auto-detect GPU/MPS
147
+ else:
148
+ device = "cpu"
149
+
150
+ self._model = CrossEncoder(
151
+ self.model_name,
152
+ device=device,
153
+ model_kwargs={"low_cpu_mem_usage": False},
154
+ )
140
155
 
141
156
  # Initialize shared executor (limited workers naturally limits concurrency)
142
157
  if LocalSTCrossEncoder._executor is None:
@@ -148,11 +163,101 @@ class LocalSTCrossEncoder(CrossEncoderModel):
148
163
  else:
149
164
  logger.info("Reranker: local provider initialized (using existing executor)")
150
165
 
166
+ def _is_xpc_error(self, error: Exception) -> bool:
167
+ """
168
+ Check if an error is an XPC connection error (macOS daemon issue).
169
+
170
+ On macOS, long-running daemons can lose XPC connections to system services
171
+ when the process is idle for extended periods.
172
+ """
173
+ error_str = str(error).lower()
174
+ return "xpc_error_connection_invalid" in error_str or "xpc error" in error_str
175
+
176
+ def _reinitialize_model_sync(self) -> None:
177
+ """
178
+ Clear and reinitialize the cross-encoder model synchronously.
179
+
180
+ This is used to recover from XPC errors on macOS where the
181
+ PyTorch/MPS backend loses its connection to system services.
182
+ """
183
+ logger.warning(f"Reinitializing reranker model {self.model_name} due to backend error")
184
+
185
+ # Clear existing model
186
+ self._model = None
187
+
188
+ # Force garbage collection to free resources
189
+ import gc
190
+
191
+ import torch
192
+
193
+ gc.collect()
194
+
195
+ # If using CUDA/MPS, clear the cache
196
+ if torch.cuda.is_available():
197
+ torch.cuda.empty_cache()
198
+ elif hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
199
+ try:
200
+ torch.mps.empty_cache()
201
+ except AttributeError:
202
+ pass # Method might not exist in all PyTorch versions
203
+
204
+ # Reinitialize the model
205
+ try:
206
+ from sentence_transformers import CrossEncoder
207
+ except ImportError:
208
+ raise ImportError(
209
+ "sentence-transformers is required for LocalSTCrossEncoder. "
210
+ "Install it with: pip install sentence-transformers"
211
+ )
212
+
213
+ # Determine device based on hardware availability
214
+ has_gpu = torch.cuda.is_available() or (hasattr(torch.backends, "mps") and torch.backends.mps.is_available())
215
+
216
+ if has_gpu:
217
+ device = None # Let sentence-transformers auto-detect GPU/MPS
218
+ else:
219
+ device = "cpu"
220
+
221
+ self._model = CrossEncoder(
222
+ self.model_name,
223
+ device=device,
224
+ model_kwargs={"low_cpu_mem_usage": False},
225
+ )
226
+
227
+ logger.info("Reranker: local provider reinitialized successfully")
228
+
229
+ def _predict_with_recovery(self, pairs: list[tuple[str, str]]) -> list[float]:
230
+ """
231
+ Predict with automatic recovery from XPC errors.
232
+
233
+ This runs synchronously in the thread pool.
234
+ """
235
+ max_retries = 1
236
+ for attempt in range(max_retries + 1):
237
+ try:
238
+ scores = self._model.predict(pairs, show_progress_bar=False)
239
+ return scores.tolist() if hasattr(scores, "tolist") else list(scores)
240
+ except Exception as e:
241
+ # Check if this is an XPC error (macOS daemon issue)
242
+ if self._is_xpc_error(e) and attempt < max_retries:
243
+ logger.warning(f"XPC error detected in reranker (attempt {attempt + 1}): {e}")
244
+ try:
245
+ self._reinitialize_model_sync()
246
+ logger.info("Reranker reinitialized successfully, retrying prediction")
247
+ continue
248
+ except Exception as reinit_error:
249
+ logger.error(f"Failed to reinitialize reranker: {reinit_error}")
250
+ raise Exception(f"Failed to recover from XPC error: {str(e)}")
251
+ else:
252
+ # Not an XPC error or out of retries
253
+ raise
254
+
151
255
  async def predict(self, pairs: list[tuple[str, str]]) -> list[float]:
152
256
  """
153
257
  Score query-document pairs for relevance.
154
258
 
155
259
  Uses a dedicated thread pool with limited workers to prevent CPU thrashing.
260
+ Automatically recovers from XPC errors on macOS by reinitializing the model.
156
261
 
157
262
  Args:
158
263
  pairs: List of (query, document) tuples to score
@@ -165,11 +270,11 @@ class LocalSTCrossEncoder(CrossEncoderModel):
165
270
 
166
271
  # Use dedicated executor - limited workers naturally limits concurrency
167
272
  loop = asyncio.get_event_loop()
168
- scores = await loop.run_in_executor(
273
+ return await loop.run_in_executor(
169
274
  LocalSTCrossEncoder._executor,
170
- lambda: self._model.predict(pairs, show_progress_bar=False),
275
+ self._predict_with_recovery,
276
+ pairs,
171
277
  )
172
- return scores.tolist() if hasattr(scores, "tolist") else list(scores)
173
278
 
174
279
 
175
280
  class RemoteTEICrossEncoder(CrossEncoderModel):
@@ -0,0 +1,5 @@
1
+ """Directives module for hard rules injected into prompts."""
2
+
3
+ from .models import Directive
4
+
5
+ __all__ = ["Directive"]
@@ -0,0 +1,37 @@
1
+ """Pydantic models for directives."""
2
+
3
+ from datetime import datetime, timezone
4
+ from uuid import UUID
5
+
6
+ from pydantic import BaseModel, Field
7
+
8
+
9
+ class Directive(BaseModel):
10
+ """A directive is a hard rule injected into prompts.
11
+
12
+ Directives are user-defined rules that guide agent behavior. Unlike mental models
13
+ which are automatically consolidated from memories, directives are explicit
14
+ instructions that are always included in relevant prompts.
15
+
16
+ Examples:
17
+ - "Always respond in formal English"
18
+ - "Never share personal data with third parties"
19
+ - "Prefer conservative investment recommendations"
20
+ """
21
+
22
+ id: UUID = Field(description="Unique identifier")
23
+ bank_id: str = Field(description="Bank this directive belongs to")
24
+ name: str = Field(description="Human-readable name")
25
+ content: str = Field(description="The directive text to inject into prompts")
26
+ priority: int = Field(default=0, description="Higher priority directives are injected first")
27
+ is_active: bool = Field(default=True, description="Whether this directive is currently active")
28
+ tags: list[str] = Field(default_factory=list, description="Tags for filtering")
29
+ created_at: datetime = Field(
30
+ default_factory=lambda: datetime.now(timezone.utc), description="When this directive was created"
31
+ )
32
+ updated_at: datetime = Field(
33
+ default_factory=lambda: datetime.now(timezone.utc), description="When this directive was last updated"
34
+ )
35
+
36
+ class Config:
37
+ from_attributes = True
@@ -128,20 +128,98 @@ class LocalSTEmbeddings(Embeddings):
128
128
  )
129
129
 
130
130
  logger.info(f"Embeddings: initializing local provider with model {self.model_name}")
131
- # Disable lazy loading (meta tensors) which causes issues with newer transformers/accelerate
132
- # Setting low_cpu_mem_usage=False and device_map=None ensures tensors are fully materialized
131
+
132
+ # Determine device based on hardware availability.
133
+ # We always set low_cpu_mem_usage=False to prevent lazy loading (meta tensors)
134
+ # which can cause issues when accelerate is installed but no GPU is available.
135
+ import torch
136
+
137
+ # Check for GPU (CUDA) or Apple Silicon (MPS)
138
+ has_gpu = torch.cuda.is_available() or (hasattr(torch.backends, "mps") and torch.backends.mps.is_available())
139
+
140
+ if has_gpu:
141
+ device = None # Let sentence-transformers auto-detect GPU/MPS
142
+ else:
143
+ device = "cpu"
144
+
133
145
  self._model = SentenceTransformer(
134
146
  self.model_name,
135
- model_kwargs={"low_cpu_mem_usage": False, "device_map": None},
147
+ device=device,
148
+ model_kwargs={"low_cpu_mem_usage": False},
136
149
  )
137
150
 
138
151
  self._dimension = self._model.get_sentence_embedding_dimension()
139
152
  logger.info(f"Embeddings: local provider initialized (dim: {self._dimension})")
140
153
 
154
+ def _is_xpc_error(self, error: Exception) -> bool:
155
+ """
156
+ Check if an error is an XPC connection error (macOS daemon issue).
157
+
158
+ On macOS, long-running daemons can lose XPC connections to system services
159
+ when the process is idle for extended periods.
160
+ """
161
+ error_str = str(error).lower()
162
+ return "xpc_error_connection_invalid" in error_str or "xpc error" in error_str
163
+
164
+ def _reinitialize_model_sync(self) -> None:
165
+ """
166
+ Clear and reinitialize the embedding model synchronously.
167
+
168
+ This is used to recover from XPC errors on macOS where the
169
+ PyTorch/MPS backend loses its connection to system services.
170
+ """
171
+ logger.warning(f"Reinitializing embedding model {self.model_name} due to backend error")
172
+
173
+ # Clear existing model
174
+ self._model = None
175
+
176
+ # Force garbage collection to free resources
177
+ import gc
178
+
179
+ import torch
180
+
181
+ gc.collect()
182
+
183
+ # If using CUDA/MPS, clear the cache
184
+ if torch.cuda.is_available():
185
+ torch.cuda.empty_cache()
186
+ elif hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
187
+ try:
188
+ torch.mps.empty_cache()
189
+ except AttributeError:
190
+ pass # Method might not exist in all PyTorch versions
191
+
192
+ # Reinitialize the model (inline version of initialize() but synchronous)
193
+ try:
194
+ from sentence_transformers import SentenceTransformer
195
+ except ImportError:
196
+ raise ImportError(
197
+ "sentence-transformers is required for LocalSTEmbeddings. "
198
+ "Install it with: pip install sentence-transformers"
199
+ )
200
+
201
+ # Determine device based on hardware availability
202
+ has_gpu = torch.cuda.is_available() or (hasattr(torch.backends, "mps") and torch.backends.mps.is_available())
203
+
204
+ if has_gpu:
205
+ device = None # Let sentence-transformers auto-detect GPU/MPS
206
+ else:
207
+ device = "cpu"
208
+
209
+ self._model = SentenceTransformer(
210
+ self.model_name,
211
+ device=device,
212
+ model_kwargs={"low_cpu_mem_usage": False},
213
+ )
214
+
215
+ logger.info("Embeddings: local provider reinitialized successfully")
216
+
141
217
  def encode(self, texts: list[str]) -> list[list[float]]:
142
218
  """
143
219
  Generate embeddings for a list of texts.
144
220
 
221
+ Automatically recovers from XPC errors on macOS by reinitializing the model.
222
+
145
223
  Args:
146
224
  texts: List of text strings to encode
147
225
 
@@ -150,8 +228,27 @@ class LocalSTEmbeddings(Embeddings):
150
228
  """
151
229
  if self._model is None:
152
230
  raise RuntimeError("Embeddings not initialized. Call initialize() first.")
153
- embeddings = self._model.encode(texts, convert_to_numpy=True, show_progress_bar=False)
154
- return [emb.tolist() for emb in embeddings]
231
+
232
+ # Try encoding with automatic recovery from XPC errors
233
+ max_retries = 1
234
+ for attempt in range(max_retries + 1):
235
+ try:
236
+ embeddings = self._model.encode(texts, convert_to_numpy=True, show_progress_bar=False)
237
+ return [emb.tolist() for emb in embeddings]
238
+ except Exception as e:
239
+ # Check if this is an XPC error (macOS daemon issue)
240
+ if self._is_xpc_error(e) and attempt < max_retries:
241
+ logger.warning(f"XPC error detected in embedding generation (attempt {attempt + 1}): {e}")
242
+ try:
243
+ self._reinitialize_model_sync()
244
+ logger.info("Model reinitialized successfully, retrying embedding generation")
245
+ continue
246
+ except Exception as reinit_error:
247
+ logger.error(f"Failed to reinitialize model: {reinit_error}")
248
+ raise Exception(f"Failed to recover from XPC error: {str(e)}")
249
+ else:
250
+ # Not an XPC error or out of retries
251
+ raise
155
252
 
156
253
 
157
254
  class RemoteTEIEmbeddings(Embeddings):
@@ -160,14 +160,14 @@ class MemoryEngineInterface(ABC):
160
160
  request_context: "RequestContext",
161
161
  ) -> dict[str, Any]:
162
162
  """
163
- Get bank profile including disposition and background.
163
+ Get bank profile including disposition and mission.
164
164
 
165
165
  Args:
166
166
  bank_id: The memory bank ID.
167
167
  request_context: Request context for authentication.
168
168
 
169
169
  Returns:
170
- Bank profile dict.
170
+ Bank profile dict with bank_id, name, disposition, and mission.
171
171
  """
172
172
  ...
173
173
 
@@ -190,25 +190,44 @@ class MemoryEngineInterface(ABC):
190
190
  ...
191
191
 
192
192
  @abstractmethod
193
- async def merge_bank_background(
193
+ async def merge_bank_mission(
194
194
  self,
195
195
  bank_id: str,
196
196
  new_info: str,
197
197
  *,
198
- update_disposition: bool = True,
199
198
  request_context: "RequestContext",
200
199
  ) -> dict[str, Any]:
201
200
  """
202
- Merge new background information into bank profile.
201
+ Merge new mission information into bank profile.
203
202
 
204
203
  Args:
205
204
  bank_id: The memory bank ID.
206
- new_info: New background information to merge.
207
- update_disposition: Whether to infer disposition from background.
205
+ new_info: New mission information to merge.
208
206
  request_context: Request context for authentication.
209
207
 
210
208
  Returns:
211
- Updated background info.
209
+ Updated mission info.
210
+ """
211
+ ...
212
+
213
+ @abstractmethod
214
+ async def set_bank_mission(
215
+ self,
216
+ bank_id: str,
217
+ mission: str,
218
+ *,
219
+ request_context: "RequestContext",
220
+ ) -> dict[str, Any]:
221
+ """
222
+ Set the bank's mission (replaces existing).
223
+
224
+ Args:
225
+ bank_id: The memory bank ID.
226
+ mission: The mission text.
227
+ request_context: Request context for authentication.
228
+
229
+ Returns:
230
+ Dict with bank_id and mission.
212
231
  """
213
232
  ...
214
233
 
@@ -518,7 +537,7 @@ class MemoryEngineInterface(ABC):
518
537
  bank_id: str,
519
538
  *,
520
539
  request_context: "RequestContext",
521
- ) -> list[dict[str, Any]]:
540
+ ) -> dict[str, Any]:
522
541
  """
523
542
  List async operations for a bank.
524
543
 
@@ -527,7 +546,7 @@ class MemoryEngineInterface(ABC):
527
546
  request_context: Request context for authentication.
528
547
 
529
548
  Returns:
530
- List of operation dicts with id, task_type, status, etc.
549
+ Dict with 'total' (int) and 'operations' (list of operation dicts).
531
550
  """
532
551
  ...
533
552
 
@@ -561,16 +580,16 @@ class MemoryEngineInterface(ABC):
561
580
  bank_id: str,
562
581
  *,
563
582
  name: str | None = None,
564
- background: str | None = None,
583
+ mission: str | None = None,
565
584
  request_context: "RequestContext",
566
585
  ) -> dict[str, Any]:
567
586
  """
568
- Update bank name and/or background.
587
+ Update bank name and/or mission.
569
588
 
570
589
  Args:
571
590
  bank_id: The memory bank ID.
572
591
  name: New bank name (optional).
573
- background: New background text (optional, replaces existing).
592
+ mission: New mission text (optional, replaces existing).
574
593
  request_context: Request context for authentication.
575
594
 
576
595
  Returns: