agno 2.3.24__py3-none-any.whl → 2.3.26__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 (70) hide show
  1. agno/agent/agent.py +357 -28
  2. agno/db/base.py +214 -0
  3. agno/db/dynamo/dynamo.py +47 -0
  4. agno/db/firestore/firestore.py +47 -0
  5. agno/db/gcs_json/gcs_json_db.py +47 -0
  6. agno/db/in_memory/in_memory_db.py +47 -0
  7. agno/db/json/json_db.py +47 -0
  8. agno/db/mongo/async_mongo.py +229 -0
  9. agno/db/mongo/mongo.py +47 -0
  10. agno/db/mongo/schemas.py +16 -0
  11. agno/db/mysql/async_mysql.py +47 -0
  12. agno/db/mysql/mysql.py +47 -0
  13. agno/db/postgres/async_postgres.py +231 -0
  14. agno/db/postgres/postgres.py +239 -0
  15. agno/db/postgres/schemas.py +19 -0
  16. agno/db/redis/redis.py +47 -0
  17. agno/db/singlestore/singlestore.py +47 -0
  18. agno/db/sqlite/async_sqlite.py +242 -0
  19. agno/db/sqlite/schemas.py +18 -0
  20. agno/db/sqlite/sqlite.py +239 -0
  21. agno/db/surrealdb/surrealdb.py +47 -0
  22. agno/knowledge/chunking/code.py +90 -0
  23. agno/knowledge/chunking/document.py +62 -2
  24. agno/knowledge/chunking/strategy.py +14 -0
  25. agno/knowledge/knowledge.py +7 -1
  26. agno/knowledge/reader/arxiv_reader.py +1 -0
  27. agno/knowledge/reader/csv_reader.py +1 -0
  28. agno/knowledge/reader/docx_reader.py +1 -0
  29. agno/knowledge/reader/firecrawl_reader.py +1 -0
  30. agno/knowledge/reader/json_reader.py +1 -0
  31. agno/knowledge/reader/markdown_reader.py +1 -0
  32. agno/knowledge/reader/pdf_reader.py +1 -0
  33. agno/knowledge/reader/pptx_reader.py +1 -0
  34. agno/knowledge/reader/s3_reader.py +1 -0
  35. agno/knowledge/reader/tavily_reader.py +1 -0
  36. agno/knowledge/reader/text_reader.py +1 -0
  37. agno/knowledge/reader/web_search_reader.py +1 -0
  38. agno/knowledge/reader/website_reader.py +1 -0
  39. agno/knowledge/reader/wikipedia_reader.py +1 -0
  40. agno/knowledge/reader/youtube_reader.py +1 -0
  41. agno/knowledge/utils.py +1 -0
  42. agno/learn/__init__.py +65 -0
  43. agno/learn/config.py +463 -0
  44. agno/learn/curate.py +185 -0
  45. agno/learn/machine.py +690 -0
  46. agno/learn/schemas.py +1043 -0
  47. agno/learn/stores/__init__.py +35 -0
  48. agno/learn/stores/entity_memory.py +3275 -0
  49. agno/learn/stores/learned_knowledge.py +1583 -0
  50. agno/learn/stores/protocol.py +117 -0
  51. agno/learn/stores/session_context.py +1217 -0
  52. agno/learn/stores/user_memory.py +1495 -0
  53. agno/learn/stores/user_profile.py +1220 -0
  54. agno/learn/utils.py +209 -0
  55. agno/models/base.py +59 -0
  56. agno/os/routers/agents/router.py +4 -4
  57. agno/os/routers/knowledge/knowledge.py +7 -0
  58. agno/os/routers/teams/router.py +3 -3
  59. agno/os/routers/workflows/router.py +5 -5
  60. agno/os/utils.py +55 -3
  61. agno/team/team.py +131 -0
  62. agno/tools/browserbase.py +78 -6
  63. agno/tools/google_bigquery.py +11 -2
  64. agno/utils/agent.py +30 -1
  65. agno/workflow/workflow.py +198 -0
  66. {agno-2.3.24.dist-info → agno-2.3.26.dist-info}/METADATA +24 -2
  67. {agno-2.3.24.dist-info → agno-2.3.26.dist-info}/RECORD +70 -56
  68. {agno-2.3.24.dist-info → agno-2.3.26.dist-info}/WHEEL +0 -0
  69. {agno-2.3.24.dist-info → agno-2.3.26.dist-info}/licenses/LICENSE +0 -0
  70. {agno-2.3.24.dist-info → agno-2.3.26.dist-info}/top_level.txt +0 -0
agno/learn/machine.py ADDED
@@ -0,0 +1,690 @@
1
+ """
2
+ Learning Machine
3
+ ================
4
+ Unified learning system for agents.
5
+
6
+ Coordinates multiple learning stores to give agents:
7
+ - User memory (who they're talking to)
8
+ - Session context (what's happened so far)
9
+ - Entity memory (knowledge about external things)
10
+ - Learned knowledge (reusable insights)
11
+
12
+ Plus maintenance via the Curator for keeping memories healthy.
13
+ """
14
+
15
+ from dataclasses import dataclass, field
16
+ from os import getenv
17
+ from typing import Any, Callable, Dict, List, Optional, Union
18
+
19
+ from agno.learn.config import (
20
+ EntityMemoryConfig,
21
+ LearnedKnowledgeConfig,
22
+ LearningMode,
23
+ SessionContextConfig,
24
+ UserMemoryConfig,
25
+ UserProfileConfig,
26
+ )
27
+ from agno.learn.curate import Curator
28
+ from agno.learn.stores.protocol import LearningStore
29
+ from agno.utils.log import (
30
+ log_debug,
31
+ log_warning,
32
+ set_log_level_to_debug,
33
+ set_log_level_to_info,
34
+ )
35
+
36
+ try:
37
+ from agno.db.base import AsyncBaseDb, BaseDb
38
+ from agno.models.base import Model
39
+ except ImportError:
40
+ pass
41
+
42
+ # Type aliases for cleaner signatures
43
+ UserProfileInput = Union[bool, UserProfileConfig, LearningStore, None]
44
+ UserMemoryInput = Union[bool, UserMemoryConfig, LearningStore, None]
45
+ EntityMemoryInput = Union[bool, EntityMemoryConfig, LearningStore, None]
46
+ SessionContextInput = Union[bool, SessionContextConfig, LearningStore, None]
47
+ LearnedKnowledgeInput = Union[bool, LearnedKnowledgeConfig, LearningStore, None]
48
+
49
+
50
+ @dataclass
51
+ class LearningMachine:
52
+ """Central orchestrator for agent learning.
53
+
54
+ Coordinates all learning stores and provides unified interface
55
+ for recall, processing, tool generation, and maintenance.
56
+
57
+ Args:
58
+ db: Database backend for persistence.
59
+ model: Model for learning extraction.
60
+ knowledge: Knowledge base for learned knowledge store.
61
+
62
+ user_profile: Enable user profile. Accepts bool, Config, or Store.
63
+ user_memory: Enable user memory. Accepts bool, Config, or Store.
64
+ session_context: Enable session context. Accepts bool, Config, or Store.
65
+ entity_memory: Enable entity memory. Accepts bool, Config, or Store.
66
+ learned_knowledge: Enable learned knowledge. Auto-enabled when knowledge provided.
67
+
68
+ namespace: Default namespace for entity_memory and learned_knowledge.
69
+ custom_stores: Additional stores implementing LearningStore protocol.
70
+ debug_mode: Enable debug logging.
71
+ """
72
+
73
+ db: Optional[Union["BaseDb", "AsyncBaseDb"]] = None
74
+ model: Optional["Model"] = None
75
+ knowledge: Optional[Any] = None
76
+
77
+ # Store configurations (accepts bool, Config, or Store instance)
78
+ user_profile: UserProfileInput = False
79
+ user_memory: UserMemoryInput = False
80
+ session_context: SessionContextInput = False
81
+ entity_memory: EntityMemoryInput = False
82
+ learned_knowledge: LearnedKnowledgeInput = False
83
+
84
+ # Namespace for entity_memory and learned_knowledge
85
+ namespace: str = "global"
86
+
87
+ # Custom stores
88
+ custom_stores: Optional[Dict[str, LearningStore]] = None
89
+
90
+ # Debug mode
91
+ debug_mode: bool = False
92
+
93
+ # Internal state (lazy initialization)
94
+ _stores: Optional[Dict[str, LearningStore]] = field(default=None, init=False)
95
+ _curator: Optional[Any] = field(default=None, init=False)
96
+
97
+ # =========================================================================
98
+ # Initialization (Lazy)
99
+ # =========================================================================
100
+
101
+ @property
102
+ def stores(self) -> Dict[str, LearningStore]:
103
+ """All registered stores, keyed by name. Lazily initialized."""
104
+ if self._stores is None:
105
+ self._initialize_stores()
106
+ return self._stores # type: ignore
107
+
108
+ def _initialize_stores(self) -> None:
109
+ """Initialize all configured stores."""
110
+ self._stores = {}
111
+
112
+ # User Profile
113
+ if self.user_profile:
114
+ self._stores["user_profile"] = self._resolve_store(
115
+ input_value=self.user_profile,
116
+ store_type="user_profile",
117
+ )
118
+
119
+ # User Memory
120
+ if self.user_memory:
121
+ self._stores["user_memory"] = self._resolve_store(
122
+ input_value=self.user_memory,
123
+ store_type="user_memory",
124
+ )
125
+
126
+ # Session Context
127
+ if self.session_context:
128
+ self._stores["session_context"] = self._resolve_store(
129
+ input_value=self.session_context,
130
+ store_type="session_context",
131
+ )
132
+
133
+ # Entity Memory
134
+ if self.entity_memory:
135
+ self._stores["entity_memory"] = self._resolve_store(
136
+ input_value=self.entity_memory,
137
+ store_type="entity_memory",
138
+ )
139
+
140
+ # Learned Knowledge (auto-enable if knowledge provided)
141
+ if self.learned_knowledge or self.knowledge is not None:
142
+ self._stores["learned_knowledge"] = self._resolve_store(
143
+ input_value=self.learned_knowledge if self.learned_knowledge else True,
144
+ store_type="learned_knowledge",
145
+ )
146
+
147
+ # Custom stores
148
+ if self.custom_stores:
149
+ for name, store in self.custom_stores.items():
150
+ self._stores[name] = store
151
+
152
+ log_debug(f"LearningMachine initialized with stores: {list(self._stores.keys())}")
153
+
154
+ def _resolve_store(
155
+ self,
156
+ input_value: Any,
157
+ store_type: str,
158
+ ) -> LearningStore:
159
+ """Resolve input to a store instance.
160
+
161
+ Args:
162
+ input_value: bool, Config, or Store instance
163
+ store_type: One of "user_profile", "user_memory", "session_context", "entity_memory", "learned_knowledge"
164
+
165
+ Returns:
166
+ Initialized store instance.
167
+ """
168
+ # Already a store instance
169
+ if isinstance(input_value, LearningStore):
170
+ return input_value
171
+
172
+ # Create store based on type
173
+ if store_type == "user_profile":
174
+ return self._create_user_profile_store(config=input_value)
175
+ elif store_type == "user_memory":
176
+ return self._create_user_memory_store(config=input_value)
177
+ elif store_type == "session_context":
178
+ return self._create_session_context_store(config=input_value)
179
+ elif store_type == "entity_memory":
180
+ return self._create_entity_memory_store(config=input_value)
181
+ elif store_type == "learned_knowledge":
182
+ return self._create_learned_knowledge_store(config=input_value)
183
+ else:
184
+ raise ValueError(f"Unknown store type: {store_type}")
185
+
186
+ def _create_user_profile_store(self, config: Any) -> LearningStore:
187
+ """Create UserProfileStore with resolved config."""
188
+ from agno.learn.stores import UserProfileStore
189
+
190
+ if isinstance(config, UserProfileConfig):
191
+ if config.db is None:
192
+ config.db = self.db
193
+ if config.model is None:
194
+ config.model = self.model
195
+ else:
196
+ config = UserProfileConfig(
197
+ db=self.db,
198
+ model=self.model,
199
+ mode=LearningMode.ALWAYS,
200
+ )
201
+
202
+ return UserProfileStore(config=config, debug_mode=self.debug_mode)
203
+
204
+ def _create_user_memory_store(self, config: Any) -> LearningStore:
205
+ """Create UserMemoryStore with resolved config."""
206
+ from agno.learn.stores import UserMemoryStore
207
+
208
+ if isinstance(config, UserMemoryConfig):
209
+ if config.db is None:
210
+ config.db = self.db
211
+ if config.model is None:
212
+ config.model = self.model
213
+ else:
214
+ config = UserMemoryConfig(
215
+ db=self.db,
216
+ model=self.model,
217
+ mode=LearningMode.ALWAYS,
218
+ )
219
+
220
+ return UserMemoryStore(config=config, debug_mode=self.debug_mode)
221
+
222
+ def _create_session_context_store(self, config: Any) -> LearningStore:
223
+ """Create SessionContextStore with resolved config."""
224
+ from agno.learn.stores import SessionContextStore
225
+
226
+ if isinstance(config, SessionContextConfig):
227
+ if config.db is None:
228
+ config.db = self.db
229
+ if config.model is None:
230
+ config.model = self.model
231
+ else:
232
+ config = SessionContextConfig(
233
+ db=self.db,
234
+ model=self.model,
235
+ enable_planning=False,
236
+ )
237
+
238
+ return SessionContextStore(config=config, debug_mode=self.debug_mode)
239
+
240
+ def _create_entity_memory_store(self, config: Any) -> LearningStore:
241
+ """Create EntityMemoryStore with resolved config."""
242
+ from agno.learn.stores import EntityMemoryStore
243
+
244
+ if isinstance(config, EntityMemoryConfig):
245
+ if config.db is None:
246
+ config.db = self.db
247
+ if config.model is None:
248
+ config.model = self.model
249
+ else:
250
+ config = EntityMemoryConfig(
251
+ db=self.db,
252
+ model=self.model,
253
+ namespace=self.namespace,
254
+ mode=LearningMode.ALWAYS,
255
+ )
256
+
257
+ return EntityMemoryStore(config=config, debug_mode=self.debug_mode)
258
+
259
+ def _create_learned_knowledge_store(self, config: Any) -> LearningStore:
260
+ """Create LearnedKnowledgeStore with resolved config."""
261
+ from agno.learn.stores import LearnedKnowledgeStore
262
+
263
+ if isinstance(config, LearnedKnowledgeConfig):
264
+ if config.model is None:
265
+ config.model = self.model
266
+ if config.knowledge is None and self.knowledge is not None:
267
+ config.knowledge = self.knowledge
268
+ else:
269
+ config = LearnedKnowledgeConfig(
270
+ model=self.model,
271
+ knowledge=self.knowledge,
272
+ mode=LearningMode.AGENTIC,
273
+ )
274
+
275
+ return LearnedKnowledgeStore(config=config, debug_mode=self.debug_mode)
276
+
277
+ # =========================================================================
278
+ # Store Accessors (Type-Safe)
279
+ # =========================================================================
280
+
281
+ @property
282
+ def user_profile_store(self) -> Optional[LearningStore]:
283
+ """Get user profile store if enabled."""
284
+ return self.stores.get("user_profile")
285
+
286
+ @property
287
+ def user_memory_store(self) -> Optional[LearningStore]:
288
+ """Get user memory store if enabled."""
289
+ return self.stores.get("user_memory")
290
+
291
+ @property
292
+ def session_context_store(self) -> Optional[LearningStore]:
293
+ """Get session context store if enabled."""
294
+ return self.stores.get("session_context")
295
+
296
+ @property
297
+ def entity_memory_store(self) -> Optional[LearningStore]:
298
+ """Get entity memory store if enabled."""
299
+ return self.stores.get("entity_memory")
300
+
301
+ @property
302
+ def learned_knowledge_store(self) -> Optional[LearningStore]:
303
+ """Get learned knowledge store if enabled."""
304
+ return self.stores.get("learned_knowledge")
305
+
306
+ @property
307
+ def was_updated(self) -> bool:
308
+ """True if any store was updated in the last operation."""
309
+ return any(getattr(store, "was_updated", False) for store in self.stores.values())
310
+
311
+ # =========================================================================
312
+ # Main API
313
+ # =========================================================================
314
+
315
+ def build_context(
316
+ self,
317
+ user_id: Optional[str] = None,
318
+ session_id: Optional[str] = None,
319
+ message: Optional[str] = None,
320
+ entity_id: Optional[str] = None,
321
+ entity_type: Optional[str] = None,
322
+ namespace: Optional[str] = None,
323
+ agent_id: Optional[str] = None,
324
+ team_id: Optional[str] = None,
325
+ **kwargs,
326
+ ) -> str:
327
+ """Build memory context for the agent's system prompt.
328
+
329
+ Call before generating a response to give the agent relevant context.
330
+
331
+ Args:
332
+ user_id: User identifier (for user profile lookup).
333
+ session_id: Session identifier (for session context lookup).
334
+ message: Current message (for semantic search of learnings).
335
+ entity_id: Entity to retrieve (for entity memory).
336
+ entity_type: Type of entity to retrieve.
337
+ namespace: Namespace filter for entity_memory and learned_knowledge.
338
+ agent_id: Optional agent context.
339
+ team_id: Optional team context.
340
+
341
+ Returns:
342
+ Context string to inject into the agent's system prompt.
343
+ """
344
+ results = self.recall(
345
+ user_id=user_id,
346
+ session_id=session_id,
347
+ message=message,
348
+ entity_id=entity_id,
349
+ entity_type=entity_type,
350
+ namespace=namespace or self.namespace,
351
+ agent_id=agent_id,
352
+ team_id=team_id,
353
+ **kwargs,
354
+ )
355
+
356
+ return self._format_results(results=results)
357
+
358
+ async def abuild_context(
359
+ self,
360
+ user_id: Optional[str] = None,
361
+ session_id: Optional[str] = None,
362
+ message: Optional[str] = None,
363
+ entity_id: Optional[str] = None,
364
+ entity_type: Optional[str] = None,
365
+ namespace: Optional[str] = None,
366
+ agent_id: Optional[str] = None,
367
+ team_id: Optional[str] = None,
368
+ **kwargs,
369
+ ) -> str:
370
+ """Async version of build_context."""
371
+ results = await self.arecall(
372
+ user_id=user_id,
373
+ session_id=session_id,
374
+ message=message,
375
+ entity_id=entity_id,
376
+ entity_type=entity_type,
377
+ namespace=namespace or self.namespace,
378
+ agent_id=agent_id,
379
+ team_id=team_id,
380
+ **kwargs,
381
+ )
382
+
383
+ return self._format_results(results=results)
384
+
385
+ def get_tools(
386
+ self,
387
+ user_id: Optional[str] = None,
388
+ session_id: Optional[str] = None,
389
+ namespace: Optional[str] = None,
390
+ agent_id: Optional[str] = None,
391
+ team_id: Optional[str] = None,
392
+ **kwargs,
393
+ ) -> List[Callable]:
394
+ """Get learning tools to expose to the agent.
395
+
396
+ Returns tools based on which stores are enabled:
397
+ - user_profile: update_user_memory
398
+ - entity_memory: search_entities, create_entity, update_entity, add_fact, etc.
399
+ - learned_knowledge: search_learnings, save_learning
400
+
401
+ Args:
402
+ user_id: User identifier (required for user profile tools).
403
+ session_id: Session identifier.
404
+ namespace: Default namespace for entity/learning operations.
405
+ agent_id: Optional agent context.
406
+ team_id: Optional team context.
407
+
408
+ Returns:
409
+ List of callable tools.
410
+ """
411
+ tools = []
412
+ context = {
413
+ "user_id": user_id,
414
+ "session_id": session_id,
415
+ "namespace": namespace or self.namespace,
416
+ "agent_id": agent_id,
417
+ "team_id": team_id,
418
+ **kwargs,
419
+ }
420
+
421
+ for name, store in self.stores.items():
422
+ try:
423
+ store_tools = store.get_tools(**context)
424
+ if store_tools:
425
+ tools.extend(store_tools)
426
+ log_debug(f"Got {len(store_tools)} tools from {name}")
427
+ except Exception as e:
428
+ log_warning(f"Error getting tools from {name}: {e}")
429
+
430
+ return tools
431
+
432
+ async def aget_tools(
433
+ self,
434
+ user_id: Optional[str] = None,
435
+ session_id: Optional[str] = None,
436
+ namespace: Optional[str] = None,
437
+ agent_id: Optional[str] = None,
438
+ team_id: Optional[str] = None,
439
+ **kwargs,
440
+ ) -> List[Callable]:
441
+ """Async version of get_tools."""
442
+ tools = []
443
+ context = {
444
+ "user_id": user_id,
445
+ "session_id": session_id,
446
+ "namespace": namespace or self.namespace,
447
+ "agent_id": agent_id,
448
+ "team_id": team_id,
449
+ **kwargs,
450
+ }
451
+
452
+ for name, store in self.stores.items():
453
+ try:
454
+ store_tools = await store.aget_tools(**context)
455
+ if store_tools:
456
+ tools.extend(store_tools)
457
+ log_debug(f"Got {len(store_tools)} tools from {name}")
458
+ except Exception as e:
459
+ log_warning(f"Error getting tools from {name}: {e}")
460
+
461
+ return tools
462
+
463
+ def process(
464
+ self,
465
+ messages: List[Any],
466
+ user_id: Optional[str] = None,
467
+ session_id: Optional[str] = None,
468
+ namespace: Optional[str] = None,
469
+ agent_id: Optional[str] = None,
470
+ team_id: Optional[str] = None,
471
+ **kwargs,
472
+ ) -> None:
473
+ """Extract and save learnings from a conversation.
474
+
475
+ Call after a conversation to extract learnings. Each store
476
+ processes based on its mode (ALWAYS stores extract automatically).
477
+
478
+ Args:
479
+ messages: Conversation messages to analyze.
480
+ user_id: User identifier (for user profile extraction).
481
+ session_id: Session identifier (for session context extraction).
482
+ namespace: Namespace for entity/learning saves.
483
+ agent_id: Optional agent context.
484
+ team_id: Optional team context.
485
+ """
486
+ context = {
487
+ "messages": messages,
488
+ "user_id": user_id,
489
+ "session_id": session_id,
490
+ "namespace": namespace or self.namespace,
491
+ "agent_id": agent_id,
492
+ "team_id": team_id,
493
+ **kwargs,
494
+ }
495
+
496
+ for name, store in self.stores.items():
497
+ try:
498
+ store.process(**context)
499
+ if getattr(store, "was_updated", False):
500
+ log_debug(f"Store {name} was updated")
501
+ except Exception as e:
502
+ log_warning(f"Error processing through {name}: {e}")
503
+
504
+ async def aprocess(
505
+ self,
506
+ messages: List[Any],
507
+ user_id: Optional[str] = None,
508
+ session_id: Optional[str] = None,
509
+ namespace: Optional[str] = None,
510
+ agent_id: Optional[str] = None,
511
+ team_id: Optional[str] = None,
512
+ **kwargs,
513
+ ) -> None:
514
+ """Async version of process."""
515
+ context = {
516
+ "messages": messages,
517
+ "user_id": user_id,
518
+ "session_id": session_id,
519
+ "namespace": namespace or self.namespace,
520
+ "agent_id": agent_id,
521
+ "team_id": team_id,
522
+ **kwargs,
523
+ }
524
+
525
+ for name, store in self.stores.items():
526
+ try:
527
+ await store.aprocess(**context)
528
+ if getattr(store, "was_updated", False):
529
+ log_debug(f"Store {name} was updated")
530
+ except Exception as e:
531
+ log_warning(f"Error processing through {name}: {e}")
532
+
533
+ # =========================================================================
534
+ # Lower-Level API
535
+ # =========================================================================
536
+
537
+ def recall(
538
+ self,
539
+ user_id: Optional[str] = None,
540
+ session_id: Optional[str] = None,
541
+ message: Optional[str] = None,
542
+ entity_id: Optional[str] = None,
543
+ entity_type: Optional[str] = None,
544
+ namespace: Optional[str] = None,
545
+ agent_id: Optional[str] = None,
546
+ team_id: Optional[str] = None,
547
+ **kwargs,
548
+ ) -> Dict[str, Any]:
549
+ """Retrieve raw data from all stores.
550
+
551
+ Most users should use `build_context()` instead.
552
+
553
+ Returns:
554
+ Dict mapping store names to their recalled data.
555
+ """
556
+ results = {}
557
+ context = {
558
+ "user_id": user_id,
559
+ "session_id": session_id,
560
+ "message": message,
561
+ "query": message, # For learned_knowledge
562
+ "entity_id": entity_id,
563
+ "entity_type": entity_type,
564
+ "namespace": namespace or self.namespace,
565
+ "agent_id": agent_id,
566
+ "team_id": team_id,
567
+ **kwargs,
568
+ }
569
+
570
+ for name, store in self.stores.items():
571
+ try:
572
+ result = store.recall(**context)
573
+ results[name] = result
574
+ try:
575
+ log_debug(f"Recalled from {name}: {result}")
576
+ except Exception:
577
+ pass
578
+ except Exception as e:
579
+ log_warning(f"Error recalling from {name}: {e}")
580
+
581
+ return results
582
+
583
+ async def arecall(
584
+ self,
585
+ user_id: Optional[str] = None,
586
+ session_id: Optional[str] = None,
587
+ message: Optional[str] = None,
588
+ entity_id: Optional[str] = None,
589
+ entity_type: Optional[str] = None,
590
+ namespace: Optional[str] = None,
591
+ agent_id: Optional[str] = None,
592
+ team_id: Optional[str] = None,
593
+ **kwargs,
594
+ ) -> Dict[str, Any]:
595
+ """Async version of recall."""
596
+ results = {}
597
+ context = {
598
+ "user_id": user_id,
599
+ "session_id": session_id,
600
+ "message": message,
601
+ "query": message,
602
+ "entity_id": entity_id,
603
+ "entity_type": entity_type,
604
+ "namespace": namespace or self.namespace,
605
+ "agent_id": agent_id,
606
+ "team_id": team_id,
607
+ **kwargs,
608
+ }
609
+
610
+ for name, store in self.stores.items():
611
+ try:
612
+ result = await store.arecall(**context)
613
+ if result is not None:
614
+ results[name] = result
615
+ try:
616
+ log_debug(f"Recalled from {name}: {result}")
617
+ except Exception:
618
+ pass
619
+ except Exception as e:
620
+ log_warning(f"Error recalling from {name}: {e}")
621
+
622
+ return results
623
+
624
+ def _format_results(self, results: Dict[str, Any]) -> str:
625
+ """Format recalled data into context string."""
626
+ parts = []
627
+
628
+ for name, data in results.items():
629
+ store = self.stores.get(name)
630
+ if store:
631
+ try:
632
+ formatted = store.build_context(data=data)
633
+ if formatted:
634
+ parts.append(formatted)
635
+ except Exception as e:
636
+ log_warning(f"Error building context from {name}: {e}")
637
+
638
+ return "\n\n".join(parts)
639
+
640
+ # =========================================================================
641
+ # Curation
642
+ # =========================================================================
643
+
644
+ @property
645
+ def curator(self) -> "Curator":
646
+ """Get the curator for memory maintenance.
647
+
648
+ Lazily creates the curator on first access.
649
+
650
+ Example:
651
+ >>> learning.curator.prune(user_id="alice", max_age_days=90)
652
+ >>> learning.curator.deduplicate(user_id="alice")
653
+ """
654
+ if self._curator is None:
655
+ from agno.learn.curate import Curator
656
+
657
+ self._curator = Curator(machine=self)
658
+ return self._curator
659
+
660
+ # =========================================================================
661
+ # Debug
662
+ # =========================================================================
663
+
664
+ def set_log_level(self) -> None:
665
+ """Set log level based on debug_mode or AGNO_DEBUG env var."""
666
+ if self.debug_mode or getenv("AGNO_DEBUG", "false").lower() == "true":
667
+ self.debug_mode = True
668
+ set_log_level_to_debug()
669
+ else:
670
+ set_log_level_to_info()
671
+
672
+ # =========================================================================
673
+ # Representation
674
+ # =========================================================================
675
+
676
+ def __repr__(self) -> str:
677
+ """String representation for debugging."""
678
+ store_names = list(self.stores.keys()) if self._stores is not None else "[not initialized]"
679
+ db_name = self.db.__class__.__name__ if self.db else None
680
+ model_name = self.model.id if self.model and hasattr(self.model, "id") else None
681
+ has_knowledge = self.knowledge is not None
682
+
683
+ return (
684
+ f"LearningMachine("
685
+ f"stores={store_names}, "
686
+ f"db={db_name}, "
687
+ f"model={model_name}, "
688
+ f"knowledge={has_knowledge}, "
689
+ f"namespace={self.namespace!r})"
690
+ )