gnosisllm-knowledge 0.2.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 (63) hide show
  1. gnosisllm_knowledge/__init__.py +91 -39
  2. gnosisllm_knowledge/api/__init__.py +3 -2
  3. gnosisllm_knowledge/api/knowledge.py +502 -32
  4. gnosisllm_knowledge/api/memory.py +966 -0
  5. gnosisllm_knowledge/backends/__init__.py +14 -5
  6. gnosisllm_knowledge/backends/memory/indexer.py +27 -2
  7. gnosisllm_knowledge/backends/memory/searcher.py +111 -10
  8. gnosisllm_knowledge/backends/opensearch/agentic.py +355 -48
  9. gnosisllm_knowledge/backends/opensearch/config.py +49 -28
  10. gnosisllm_knowledge/backends/opensearch/indexer.py +49 -3
  11. gnosisllm_knowledge/backends/opensearch/mappings.py +14 -5
  12. gnosisllm_knowledge/backends/opensearch/memory/__init__.py +12 -0
  13. gnosisllm_knowledge/backends/opensearch/memory/client.py +1380 -0
  14. gnosisllm_knowledge/backends/opensearch/memory/config.py +127 -0
  15. gnosisllm_knowledge/backends/opensearch/memory/setup.py +322 -0
  16. gnosisllm_knowledge/backends/opensearch/queries.py +33 -33
  17. gnosisllm_knowledge/backends/opensearch/searcher.py +238 -0
  18. gnosisllm_knowledge/backends/opensearch/setup.py +308 -148
  19. gnosisllm_knowledge/cli/app.py +436 -31
  20. gnosisllm_knowledge/cli/commands/agentic.py +26 -9
  21. gnosisllm_knowledge/cli/commands/load.py +169 -19
  22. gnosisllm_knowledge/cli/commands/memory.py +733 -0
  23. gnosisllm_knowledge/cli/commands/search.py +9 -10
  24. gnosisllm_knowledge/cli/commands/setup.py +49 -23
  25. gnosisllm_knowledge/cli/display/service.py +43 -0
  26. gnosisllm_knowledge/cli/utils/config.py +62 -4
  27. gnosisllm_knowledge/core/domain/__init__.py +54 -0
  28. gnosisllm_knowledge/core/domain/discovery.py +166 -0
  29. gnosisllm_knowledge/core/domain/document.py +19 -19
  30. gnosisllm_knowledge/core/domain/memory.py +440 -0
  31. gnosisllm_knowledge/core/domain/result.py +11 -3
  32. gnosisllm_knowledge/core/domain/search.py +12 -25
  33. gnosisllm_knowledge/core/domain/source.py +11 -12
  34. gnosisllm_knowledge/core/events/__init__.py +8 -0
  35. gnosisllm_knowledge/core/events/types.py +198 -5
  36. gnosisllm_knowledge/core/exceptions.py +227 -0
  37. gnosisllm_knowledge/core/interfaces/__init__.py +17 -0
  38. gnosisllm_knowledge/core/interfaces/agentic.py +11 -3
  39. gnosisllm_knowledge/core/interfaces/indexer.py +10 -1
  40. gnosisllm_knowledge/core/interfaces/memory.py +524 -0
  41. gnosisllm_knowledge/core/interfaces/searcher.py +10 -1
  42. gnosisllm_knowledge/core/interfaces/streaming.py +133 -0
  43. gnosisllm_knowledge/core/streaming/__init__.py +36 -0
  44. gnosisllm_knowledge/core/streaming/pipeline.py +228 -0
  45. gnosisllm_knowledge/fetchers/__init__.py +8 -0
  46. gnosisllm_knowledge/fetchers/config.py +27 -0
  47. gnosisllm_knowledge/fetchers/neoreader.py +31 -3
  48. gnosisllm_knowledge/fetchers/neoreader_discovery.py +505 -0
  49. gnosisllm_knowledge/loaders/__init__.py +5 -1
  50. gnosisllm_knowledge/loaders/base.py +3 -4
  51. gnosisllm_knowledge/loaders/discovery.py +338 -0
  52. gnosisllm_knowledge/loaders/discovery_streaming.py +343 -0
  53. gnosisllm_knowledge/loaders/factory.py +46 -0
  54. gnosisllm_knowledge/loaders/sitemap.py +129 -1
  55. gnosisllm_knowledge/loaders/sitemap_streaming.py +258 -0
  56. gnosisllm_knowledge/services/indexing.py +100 -93
  57. gnosisllm_knowledge/services/search.py +84 -31
  58. gnosisllm_knowledge/services/streaming_pipeline.py +334 -0
  59. {gnosisllm_knowledge-0.2.0.dist-info → gnosisllm_knowledge-0.4.0.dist-info}/METADATA +73 -10
  60. gnosisllm_knowledge-0.4.0.dist-info/RECORD +81 -0
  61. gnosisllm_knowledge-0.2.0.dist-info/RECORD +0 -64
  62. {gnosisllm_knowledge-0.2.0.dist-info → gnosisllm_knowledge-0.4.0.dist-info}/WHEEL +0 -0
  63. {gnosisllm_knowledge-0.2.0.dist-info → gnosisllm_knowledge-0.4.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,966 @@
1
+ """High-level Memory API facade."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from datetime import datetime
7
+ from typing import Any
8
+
9
+ from gnosisllm_knowledge.backends.opensearch.memory.client import OpenSearchMemoryClient
10
+ from gnosisllm_knowledge.backends.opensearch.memory.config import MemoryConfig
11
+ from gnosisllm_knowledge.core.domain.memory import (
12
+ ContainerConfig,
13
+ ContainerInfo,
14
+ HistoryEntry,
15
+ MemoryEntry,
16
+ MemoryStats,
17
+ MemoryStrategy,
18
+ MemoryType,
19
+ Message,
20
+ Namespace,
21
+ PayloadType,
22
+ RecallResult,
23
+ SessionInfo,
24
+ StoreRequest,
25
+ StoreResult,
26
+ StrategyConfig,
27
+ )
28
+ from gnosisllm_knowledge.core.events.emitter import EventEmitter
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+
33
+ class Memory:
34
+ """High-level facade for Agentic Memory operations.
35
+
36
+ Provides a developer-friendly interface for storing, recalling, and
37
+ managing conversational memories using OpenSearch Agentic Memory.
38
+
39
+ Example:
40
+ ```python
41
+ from gnosisllm_knowledge import Memory, MemoryStrategy, StrategyConfig, Message
42
+
43
+ # Quick start from environment
44
+ memory = Memory.from_env()
45
+
46
+ # Create a container with strategy-to-namespace mapping
47
+ container = await memory.create_container(
48
+ name="agent-memory",
49
+ strategies=[
50
+ StrategyConfig(type=MemoryStrategy.SEMANTIC, namespace=["user_id"]),
51
+ StrategyConfig(type=MemoryStrategy.USER_PREFERENCE, namespace=["user_id"]),
52
+ StrategyConfig(type=MemoryStrategy.SUMMARY, namespace=["session_id"]),
53
+ ],
54
+ )
55
+
56
+ # Store conversation
57
+ await memory.store(
58
+ container_id=container.id,
59
+ messages=[
60
+ Message(role="user", content="I'm Sarah Chen, VP of Engineering"),
61
+ Message(role="assistant", content="Hello Sarah!"),
62
+ ],
63
+ user_id="sarah-123",
64
+ infer=True,
65
+ )
66
+
67
+ # Recall memories
68
+ result = await memory.recall(
69
+ container_id=container.id,
70
+ query="user information",
71
+ user_id="sarah-123",
72
+ )
73
+ for entry in result.items:
74
+ print(f"[{entry.strategy}] {entry.content}")
75
+ ```
76
+ """
77
+
78
+ def __init__(
79
+ self,
80
+ *,
81
+ client: OpenSearchMemoryClient,
82
+ config: MemoryConfig,
83
+ events: EventEmitter | None = None,
84
+ ) -> None:
85
+ """Initialize Memory facade.
86
+
87
+ Args:
88
+ client: OpenSearch memory client.
89
+ config: Memory configuration.
90
+ events: Optional event emitter.
91
+ """
92
+ self._client = client
93
+ self._config = config
94
+ self._events = events or EventEmitter()
95
+
96
+ @classmethod
97
+ def from_opensearch(
98
+ cls,
99
+ host: str = "localhost",
100
+ port: int = 9200,
101
+ *,
102
+ username: str | None = None,
103
+ password: str | None = None,
104
+ use_ssl: bool = False,
105
+ verify_certs: bool = True,
106
+ llm_model_id: str | None = None,
107
+ embedding_model_id: str | None = None,
108
+ llm_result_path: str = "$.choices[0].message.content",
109
+ config: MemoryConfig | None = None,
110
+ **kwargs: Any,
111
+ ) -> Memory:
112
+ """Create Memory instance with OpenSearch backend.
113
+
114
+ Args:
115
+ host: OpenSearch host.
116
+ port: OpenSearch port.
117
+ username: Optional username.
118
+ password: Optional password.
119
+ use_ssl: Use SSL connection.
120
+ verify_certs: Verify SSL certificates.
121
+ llm_model_id: LLM model ID for inference.
122
+ embedding_model_id: Embedding model ID.
123
+ llm_result_path: JSONPath to extract LLM response.
124
+ config: Optional MemoryConfig (overrides other params).
125
+ **kwargs: Additional config options.
126
+
127
+ Returns:
128
+ Configured Memory instance.
129
+ """
130
+ if config is None:
131
+ config = MemoryConfig(
132
+ host=host,
133
+ port=port,
134
+ username=username,
135
+ password=password,
136
+ use_ssl=use_ssl,
137
+ verify_certs=verify_certs,
138
+ llm_model_id=llm_model_id,
139
+ embedding_model_id=embedding_model_id,
140
+ llm_result_path=llm_result_path,
141
+ **kwargs,
142
+ )
143
+
144
+ client = OpenSearchMemoryClient(config)
145
+ return cls(client=client, config=config)
146
+
147
+ @classmethod
148
+ def from_env(cls) -> Memory:
149
+ """Create Memory instance from environment variables.
150
+
151
+ Environment Variables:
152
+ OPENSEARCH_HOST: OpenSearch host (default: localhost)
153
+ OPENSEARCH_PORT: OpenSearch port (default: 9200)
154
+ OPENSEARCH_USERNAME: Username
155
+ OPENSEARCH_PASSWORD: Password
156
+ OPENSEARCH_USE_SSL: Use SSL (default: false)
157
+ OPENSEARCH_VERIFY_CERTS: Verify certs (default: true)
158
+ OPENSEARCH_LLM_MODEL_ID: LLM model ID for inference
159
+ OPENSEARCH_EMBEDDING_MODEL_ID: Embedding model ID
160
+ OPENSEARCH_LLM_RESULT_PATH: JSONPath for LLM response
161
+
162
+ Returns:
163
+ Configured Memory instance.
164
+ """
165
+ config = MemoryConfig.from_env()
166
+ client = OpenSearchMemoryClient(config)
167
+ return cls(client=client, config=config)
168
+
169
+ @classmethod
170
+ def from_config(cls, config: MemoryConfig) -> Memory:
171
+ """Create Memory instance from configuration.
172
+
173
+ Args:
174
+ config: Memory configuration.
175
+
176
+ Returns:
177
+ Configured Memory instance.
178
+ """
179
+ client = OpenSearchMemoryClient(config)
180
+ return cls(client=client, config=config)
181
+
182
+ @property
183
+ def events(self) -> EventEmitter:
184
+ """Get the event emitter."""
185
+ return self._events
186
+
187
+ @property
188
+ def is_configured(self) -> bool:
189
+ """Check if memory is properly configured for inference.
190
+
191
+ Returns:
192
+ True if both LLM and embedding models are configured.
193
+ """
194
+ return self._config.is_configured
195
+
196
+ # === Container Management ===
197
+
198
+ async def create_container(
199
+ self,
200
+ name: str,
201
+ *,
202
+ strategies: list[StrategyConfig],
203
+ description: str | None = None,
204
+ llm_model_id: str | None = None,
205
+ embedding_model_id: str | None = None,
206
+ embedding_dimension: int = 1536,
207
+ **options: Any,
208
+ ) -> ContainerInfo:
209
+ """Create a new memory container.
210
+
211
+ Args:
212
+ name: Container name.
213
+ strategies: Strategy configurations with namespace scoping (REQUIRED).
214
+ description: Optional description.
215
+ llm_model_id: Override LLM model ID.
216
+ embedding_model_id: Override embedding model ID.
217
+ embedding_dimension: Embedding dimension.
218
+ **options: Additional options.
219
+
220
+ Returns:
221
+ Created container info.
222
+
223
+ Raises:
224
+ ValueError: If strategies is empty.
225
+
226
+ Example:
227
+ ```python
228
+ container = await memory.create_container(
229
+ name="agent-memory",
230
+ strategies=[
231
+ StrategyConfig(type=MemoryStrategy.SEMANTIC, namespace=["user_id"]),
232
+ StrategyConfig(type=MemoryStrategy.SUMMARY, namespace=["session_id"]),
233
+ ],
234
+ )
235
+ ```
236
+ """
237
+ if not strategies:
238
+ raise ValueError(
239
+ "strategies is required - each strategy must be scoped to namespace fields"
240
+ )
241
+
242
+ container_config = ContainerConfig(
243
+ name=name,
244
+ description=description,
245
+ strategies=strategies,
246
+ llm_model_id=llm_model_id or self._config.llm_model_id,
247
+ embedding_model_id=embedding_model_id or self._config.embedding_model_id,
248
+ embedding_dimension=embedding_dimension,
249
+ llm_result_path=self._config.llm_result_path,
250
+ )
251
+
252
+ return await self._client.create_container(container_config, **options)
253
+
254
+ async def get_container(self, container_id: str) -> ContainerInfo | None:
255
+ """Get container by ID.
256
+
257
+ Args:
258
+ container_id: Container ID.
259
+
260
+ Returns:
261
+ Container info or None if not found.
262
+ """
263
+ return await self._client.get_container(container_id)
264
+
265
+ async def list_containers(self, limit: int = 100) -> list[ContainerInfo]:
266
+ """List all containers.
267
+
268
+ Args:
269
+ limit: Maximum number of containers to return.
270
+
271
+ Returns:
272
+ List of container info.
273
+ """
274
+ return await self._client.list_containers(limit)
275
+
276
+ async def delete_container(self, container_id: str) -> bool:
277
+ """Delete a container.
278
+
279
+ Args:
280
+ container_id: Container ID.
281
+
282
+ Returns:
283
+ True if deleted, False if not found.
284
+ """
285
+ return await self._client.delete_container(container_id)
286
+
287
+ async def update_container(
288
+ self,
289
+ container_id: str,
290
+ *,
291
+ description: str | None = None,
292
+ strategies: list[StrategyConfig] | None = None,
293
+ **options: Any,
294
+ ) -> ContainerInfo:
295
+ """Update a container.
296
+
297
+ Args:
298
+ container_id: Container ID.
299
+ description: Updated description.
300
+ strategies: Updated strategy configurations.
301
+ **options: Additional options.
302
+
303
+ Returns:
304
+ Updated container info.
305
+ """
306
+ config = ContainerConfig(
307
+ name="", # Not updated
308
+ description=description,
309
+ strategies=strategies or [],
310
+ )
311
+ return await self._client.update_container(container_id, config, **options)
312
+
313
+ # === Memory Storage ===
314
+
315
+ async def store(
316
+ self,
317
+ container_id: str,
318
+ messages: list[Message] | None = None,
319
+ *,
320
+ structured_data: dict[str, Any] | None = None,
321
+ namespace: Namespace | dict[str, str] | None = None,
322
+ user_id: str | None = None,
323
+ session_id: str | None = None,
324
+ agent_id: str | None = None,
325
+ infer: bool = True,
326
+ metadata: dict[str, Any] | None = None,
327
+ tags: dict[str, str] | None = None,
328
+ **options: Any,
329
+ ) -> StoreResult:
330
+ """Store conversation or data with optional LLM inference.
331
+
332
+ Which strategies run is determined by:
333
+ 1. How the container was configured (strategy -> namespace field mapping)
334
+ 2. Which namespace fields are present in the request
335
+
336
+ Args:
337
+ container_id: Target container ID.
338
+ messages: Conversation messages.
339
+ structured_data: Structured data (alternative to messages).
340
+ namespace: Full namespace or dict.
341
+ user_id: Shorthand for namespace user_id.
342
+ session_id: Shorthand for namespace session_id.
343
+ agent_id: Shorthand for namespace agent_id.
344
+ infer: Enable LLM inference for fact extraction.
345
+ metadata: Custom metadata.
346
+ tags: Custom tags.
347
+ **options: Additional options.
348
+
349
+ Returns:
350
+ Store result with IDs and counts.
351
+
352
+ Example:
353
+ ```python
354
+ # Store with namespace shorthand
355
+ await memory.store(
356
+ container_id=container.id,
357
+ messages=[
358
+ Message(role="user", content="I prefer dark mode"),
359
+ Message(role="assistant", content="Dark mode enabled!"),
360
+ ],
361
+ user_id="alice-123",
362
+ infer=True,
363
+ )
364
+
365
+ # Store with full namespace
366
+ await memory.store(
367
+ container_id=container.id,
368
+ messages=messages,
369
+ namespace={"user_id": "alice", "org_id": "acme"},
370
+ )
371
+ ```
372
+ """
373
+ # Build namespace
374
+ if isinstance(namespace, dict):
375
+ ns = Namespace(namespace)
376
+ elif namespace is None:
377
+ ns = Namespace()
378
+ else:
379
+ ns = namespace
380
+
381
+ # Add shorthand values
382
+ if user_id:
383
+ ns["user_id"] = user_id
384
+ if session_id:
385
+ ns["session_id"] = session_id
386
+ if agent_id:
387
+ ns["agent_id"] = agent_id
388
+
389
+ # Determine payload type
390
+ payload_type = PayloadType.CONVERSATIONAL if messages else PayloadType.DATA
391
+
392
+ request = StoreRequest(
393
+ messages=messages,
394
+ structured_data=structured_data,
395
+ namespace=ns,
396
+ payload_type=payload_type,
397
+ infer=infer,
398
+ metadata=metadata or {},
399
+ tags=tags or {},
400
+ )
401
+
402
+ return await self._client.store(container_id, request, **options)
403
+
404
+ # === Memory Recall ===
405
+
406
+ async def recall(
407
+ self,
408
+ container_id: str,
409
+ query: str,
410
+ *,
411
+ namespace: Namespace | dict[str, str] | None = None,
412
+ user_id: str | None = None,
413
+ session_id: str | None = None,
414
+ agent_id: str | None = None,
415
+ strategies: list[MemoryStrategy] | None = None,
416
+ min_score: float | None = None,
417
+ limit: int = 10,
418
+ after: datetime | None = None,
419
+ before: datetime | None = None,
420
+ **options: Any,
421
+ ) -> RecallResult:
422
+ """Semantic search over long-term memories.
423
+
424
+ Args:
425
+ container_id: Container ID.
426
+ query: Search query text.
427
+ namespace: Full namespace or dict.
428
+ user_id: Shorthand for namespace user_id.
429
+ session_id: Shorthand for namespace session_id.
430
+ agent_id: Shorthand for namespace agent_id.
431
+ strategies: Filter by specific strategies.
432
+ min_score: Minimum similarity score.
433
+ limit: Maximum results.
434
+ after: Filter by created after timestamp.
435
+ before: Filter by created before timestamp.
436
+ **options: Additional options.
437
+
438
+ Returns:
439
+ Recall result with memory entries.
440
+
441
+ Example:
442
+ ```python
443
+ result = await memory.recall(
444
+ container_id=container.id,
445
+ query="user preferences",
446
+ user_id="alice-123",
447
+ limit=5,
448
+ )
449
+ for entry in result.items:
450
+ print(f"{entry.content} (score: {entry.score})")
451
+ ```
452
+ """
453
+ # Build namespace
454
+ if isinstance(namespace, dict):
455
+ ns = Namespace(namespace)
456
+ elif namespace is None:
457
+ ns = Namespace()
458
+ else:
459
+ ns = namespace
460
+
461
+ if user_id:
462
+ ns["user_id"] = user_id
463
+ if session_id:
464
+ ns["session_id"] = session_id
465
+ if agent_id:
466
+ ns["agent_id"] = agent_id
467
+
468
+ return await self._client.recall(
469
+ container_id=container_id,
470
+ query=query,
471
+ namespace=ns if ns.values else None,
472
+ strategies=strategies,
473
+ min_score=min_score,
474
+ limit=limit,
475
+ after=after,
476
+ before=before,
477
+ **options,
478
+ )
479
+
480
+ async def get_memory(
481
+ self,
482
+ container_id: str,
483
+ memory_id: str,
484
+ memory_type: MemoryType,
485
+ **options: Any,
486
+ ) -> MemoryEntry | None:
487
+ """Get a specific memory by ID.
488
+
489
+ Args:
490
+ container_id: Container ID.
491
+ memory_id: Memory document ID.
492
+ memory_type: Memory type (WORKING or LONG_TERM).
493
+ **options: Additional options.
494
+
495
+ Returns:
496
+ Memory entry or None if not found.
497
+ """
498
+ return await self._client.get_memory(
499
+ container_id=container_id,
500
+ memory_id=memory_id,
501
+ memory_type=memory_type,
502
+ **options,
503
+ )
504
+
505
+ async def delete_memory(
506
+ self,
507
+ container_id: str,
508
+ memory_id: str,
509
+ memory_type: MemoryType,
510
+ **options: Any,
511
+ ) -> bool:
512
+ """Delete a specific memory by ID.
513
+
514
+ Args:
515
+ container_id: Container ID.
516
+ memory_id: Memory document ID.
517
+ memory_type: Memory type (WORKING or LONG_TERM).
518
+ **options: Additional options.
519
+
520
+ Returns:
521
+ True if deleted, False if not found.
522
+ """
523
+ return await self._client.delete_memory(
524
+ container_id=container_id,
525
+ memory_id=memory_id,
526
+ memory_type=memory_type,
527
+ **options,
528
+ )
529
+
530
+ async def update_memory(
531
+ self,
532
+ container_id: str,
533
+ memory_id: str,
534
+ memory_type: MemoryType,
535
+ *,
536
+ memory: str | None = None,
537
+ tags: dict[str, str] | None = None,
538
+ **options: Any,
539
+ ) -> MemoryEntry:
540
+ """Update a specific memory.
541
+
542
+ Note: History memory type does NOT support updates.
543
+
544
+ Args:
545
+ container_id: Container ID.
546
+ memory_id: Memory document ID.
547
+ memory_type: Memory type (working, long-term, sessions).
548
+ memory: Updated memory content (for long-term).
549
+ tags: Updated tags.
550
+ **options: Additional options.
551
+
552
+ Returns:
553
+ Updated memory entry.
554
+ """
555
+ return await self._client.update_memory(
556
+ container_id=container_id,
557
+ memory_id=memory_id,
558
+ memory_type=memory_type,
559
+ memory=memory,
560
+ tags=tags,
561
+ **options,
562
+ )
563
+
564
+ async def clear_working_memory(
565
+ self,
566
+ container_id: str,
567
+ session_id: str | None = None,
568
+ namespace: Namespace | dict[str, str] | None = None,
569
+ user_id: str | None = None,
570
+ **options: Any,
571
+ ) -> int:
572
+ """Clear working memory.
573
+
574
+ Args:
575
+ container_id: Container ID.
576
+ session_id: Optional session filter.
577
+ namespace: Full namespace or dict.
578
+ user_id: Shorthand for namespace user_id.
579
+ **options: Additional options.
580
+
581
+ Returns:
582
+ Number of messages deleted.
583
+ """
584
+ if isinstance(namespace, dict):
585
+ ns = Namespace(namespace)
586
+ elif namespace is None:
587
+ ns = Namespace()
588
+ else:
589
+ ns = namespace
590
+
591
+ if user_id:
592
+ ns["user_id"] = user_id
593
+
594
+ return await self._client.clear_working_memory(
595
+ container_id=container_id,
596
+ session_id=session_id,
597
+ namespace=ns if ns.values else None,
598
+ **options,
599
+ )
600
+
601
+ async def delete_memories(
602
+ self,
603
+ container_id: str,
604
+ session_id: str | None = None,
605
+ namespace: Namespace | dict[str, str] | None = None,
606
+ user_id: str | None = None,
607
+ before: datetime | None = None,
608
+ memory_type: MemoryType = MemoryType.WORKING,
609
+ **options: Any,
610
+ ) -> int:
611
+ """Delete memories by filter.
612
+
613
+ Args:
614
+ container_id: Container ID.
615
+ session_id: Optional session filter.
616
+ namespace: Full namespace or dict.
617
+ user_id: Shorthand for namespace user_id.
618
+ before: Delete memories created before this timestamp.
619
+ memory_type: Memory type to delete (default: WORKING).
620
+ **options: Additional options.
621
+
622
+ Returns:
623
+ Number of memories deleted.
624
+ """
625
+ if isinstance(namespace, dict):
626
+ ns = Namespace(namespace)
627
+ elif namespace is None:
628
+ ns = Namespace()
629
+ else:
630
+ ns = namespace
631
+
632
+ if user_id:
633
+ ns["user_id"] = user_id
634
+
635
+ return await self._client.delete_memories(
636
+ container_id=container_id,
637
+ session_id=session_id,
638
+ namespace=ns if ns.values else None,
639
+ before=before,
640
+ memory_type=memory_type,
641
+ **options,
642
+ )
643
+
644
+ async def delete_by_query(
645
+ self,
646
+ container_id: str,
647
+ memory_type: MemoryType,
648
+ query: dict[str, Any],
649
+ **options: Any,
650
+ ) -> int:
651
+ """Delete memories matching an OpenSearch Query DSL query.
652
+
653
+ Provides full flexibility for complex deletion criteria.
654
+
655
+ Args:
656
+ container_id: Container ID.
657
+ memory_type: Memory type to delete from.
658
+ query: OpenSearch Query DSL query.
659
+ **options: Additional options.
660
+
661
+ Returns:
662
+ Number of documents deleted.
663
+
664
+ Example:
665
+ ```python
666
+ # Delete all memories for a user older than 30 days
667
+ await memory.delete_by_query(
668
+ container_id=container_id,
669
+ memory_type=MemoryType.WORKING,
670
+ query={
671
+ "bool": {
672
+ "must": [
673
+ {"term": {"namespace.user_id": "user123"}},
674
+ {"range": {"created_time": {"lt": "now-30d"}}}
675
+ ]
676
+ }
677
+ }
678
+ )
679
+ ```
680
+ """
681
+ return await self._client.delete_by_query(
682
+ container_id=container_id,
683
+ memory_type=memory_type,
684
+ query=query,
685
+ **options,
686
+ )
687
+
688
+ # === Working Memory ===
689
+
690
+ async def get_working_memory(
691
+ self,
692
+ container_id: str,
693
+ session_id: str | None = None,
694
+ *,
695
+ namespace: Namespace | dict[str, str] | None = None,
696
+ limit: int = 50,
697
+ offset: int = 0,
698
+ **options: Any,
699
+ ) -> list[Message]:
700
+ """Get working memory messages.
701
+
702
+ Args:
703
+ container_id: Container ID.
704
+ session_id: Optional session filter.
705
+ namespace: Optional namespace filter.
706
+ limit: Maximum messages to return.
707
+ offset: Number of messages to skip.
708
+ **options: Additional options.
709
+
710
+ Returns:
711
+ List of messages.
712
+ """
713
+ ns = Namespace(namespace) if isinstance(namespace, dict) else namespace
714
+
715
+ return await self._client.get_working_memory(
716
+ container_id=container_id,
717
+ session_id=session_id,
718
+ namespace=ns,
719
+ limit=limit,
720
+ offset=offset,
721
+ **options,
722
+ )
723
+
724
+ # === Session Management ===
725
+
726
+ async def create_session(
727
+ self,
728
+ container_id: str,
729
+ *,
730
+ session_id: str | None = None,
731
+ summary: str | None = None,
732
+ namespace: Namespace | dict[str, str] | None = None,
733
+ user_id: str | None = None,
734
+ agent_id: str | None = None,
735
+ metadata: dict[str, Any] | None = None,
736
+ **options: Any,
737
+ ) -> SessionInfo:
738
+ """Create a new session.
739
+
740
+ Args:
741
+ container_id: Container ID.
742
+ session_id: Custom session ID (auto-generated if not provided).
743
+ summary: Session summary text.
744
+ namespace: Full namespace or dict.
745
+ user_id: Shorthand for namespace user_id.
746
+ agent_id: Shorthand for namespace agent_id.
747
+ metadata: Custom metadata.
748
+ **options: Additional options.
749
+
750
+ Returns:
751
+ Created session info.
752
+ """
753
+ if isinstance(namespace, dict):
754
+ ns = Namespace(namespace)
755
+ elif namespace is None:
756
+ ns = Namespace()
757
+ else:
758
+ ns = namespace
759
+
760
+ if user_id:
761
+ ns["user_id"] = user_id
762
+ if agent_id:
763
+ ns["agent_id"] = agent_id
764
+
765
+ return await self._client.create_session(
766
+ container_id=container_id,
767
+ session_id=session_id,
768
+ summary=summary,
769
+ namespace=ns if ns.values else None,
770
+ metadata=metadata,
771
+ **options,
772
+ )
773
+
774
+ async def get_session(
775
+ self,
776
+ container_id: str,
777
+ session_id: str,
778
+ *,
779
+ include_messages: bool = False,
780
+ message_limit: int = 50,
781
+ **options: Any,
782
+ ) -> SessionInfo | None:
783
+ """Get session by ID.
784
+
785
+ Args:
786
+ container_id: Container ID.
787
+ session_id: Session ID.
788
+ include_messages: Include session messages.
789
+ message_limit: Max messages to include.
790
+ **options: Additional options.
791
+
792
+ Returns:
793
+ Session info or None if not found.
794
+ """
795
+ return await self._client.get_session(
796
+ container_id=container_id,
797
+ session_id=session_id,
798
+ include_messages=include_messages,
799
+ message_limit=message_limit,
800
+ **options,
801
+ )
802
+
803
+ async def list_sessions(
804
+ self,
805
+ container_id: str,
806
+ *,
807
+ namespace: Namespace | dict[str, str] | None = None,
808
+ user_id: str | None = None,
809
+ limit: int = 100,
810
+ **options: Any,
811
+ ) -> list[SessionInfo]:
812
+ """List sessions.
813
+
814
+ Args:
815
+ container_id: Container ID.
816
+ namespace: Full namespace or dict.
817
+ user_id: Shorthand for namespace user_id.
818
+ limit: Maximum sessions to return.
819
+ **options: Additional options.
820
+
821
+ Returns:
822
+ List of session info.
823
+ """
824
+ if isinstance(namespace, dict):
825
+ ns = Namespace(namespace)
826
+ elif namespace is None:
827
+ ns = Namespace()
828
+ else:
829
+ ns = namespace
830
+
831
+ if user_id:
832
+ ns["user_id"] = user_id
833
+
834
+ return await self._client.list_sessions(
835
+ container_id=container_id,
836
+ namespace=ns if ns.values else None,
837
+ limit=limit,
838
+ **options,
839
+ )
840
+
841
+ async def update_session(
842
+ self,
843
+ container_id: str,
844
+ session_id: str,
845
+ *,
846
+ summary: str | None = None,
847
+ metadata: dict[str, Any] | None = None,
848
+ **options: Any,
849
+ ) -> SessionInfo:
850
+ """Update a session.
851
+
852
+ Args:
853
+ container_id: Container ID.
854
+ session_id: Session ID.
855
+ summary: Updated summary text.
856
+ metadata: Updated metadata.
857
+ **options: Additional options.
858
+
859
+ Returns:
860
+ Updated session info.
861
+ """
862
+ return await self._client.update_session(
863
+ container_id=container_id,
864
+ session_id=session_id,
865
+ summary=summary,
866
+ metadata=metadata,
867
+ **options,
868
+ )
869
+
870
+ async def delete_session(
871
+ self,
872
+ container_id: str,
873
+ session_id: str,
874
+ **options: Any,
875
+ ) -> bool:
876
+ """Delete a session.
877
+
878
+ Args:
879
+ container_id: Container ID.
880
+ session_id: Session ID.
881
+ **options: Additional options.
882
+
883
+ Returns:
884
+ True if deleted, False if not found.
885
+ """
886
+ return await self._client.delete_session(
887
+ container_id=container_id,
888
+ session_id=session_id,
889
+ **options,
890
+ )
891
+
892
+ # === History (Audit Trail) ===
893
+
894
+ async def get_history_entry(
895
+ self,
896
+ container_id: str,
897
+ history_id: str,
898
+ **options: Any,
899
+ ) -> HistoryEntry | None:
900
+ """Get a specific history entry by ID.
901
+
902
+ History entries are READ-ONLY audit trail records.
903
+
904
+ Args:
905
+ container_id: Container ID.
906
+ history_id: History entry ID.
907
+ **options: Additional options.
908
+
909
+ Returns:
910
+ History entry or None if not found.
911
+ """
912
+ return await self._client.get_history_entry(
913
+ container_id=container_id,
914
+ history_id=history_id,
915
+ **options,
916
+ )
917
+
918
+ async def list_history(
919
+ self,
920
+ container_id: str,
921
+ memory_id: str | None = None,
922
+ namespace: Namespace | dict[str, str] | None = None,
923
+ limit: int = 100,
924
+ **options: Any,
925
+ ) -> list[HistoryEntry]:
926
+ """List history entries.
927
+
928
+ Args:
929
+ container_id: Container ID.
930
+ memory_id: Filter by specific memory ID.
931
+ namespace: Optional namespace filter.
932
+ limit: Maximum entries to return.
933
+ **options: Additional options.
934
+
935
+ Returns:
936
+ List of history entries (most recent first).
937
+ """
938
+ ns = Namespace(namespace) if isinstance(namespace, dict) else namespace
939
+
940
+ return await self._client.list_history(
941
+ container_id=container_id,
942
+ memory_id=memory_id,
943
+ namespace=ns,
944
+ limit=limit,
945
+ **options,
946
+ )
947
+
948
+ # === Statistics ===
949
+
950
+ async def get_stats(self, container_id: str) -> MemoryStats:
951
+ """Get container statistics.
952
+
953
+ Args:
954
+ container_id: Container ID.
955
+
956
+ Returns:
957
+ Memory statistics for the container.
958
+ """
959
+ return await self._client.get_stats(container_id)
960
+
961
+ # === Cleanup ===
962
+
963
+ async def close(self) -> None:
964
+ """Close connections and clean up resources."""
965
+ # Currently no resources to clean up, but method is here for future use
966
+ pass