agentrun-sdk 0.1.2__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.

Potentially problematic release.


This version of agentrun-sdk might be problematic. Click here for more details.

Files changed (115) hide show
  1. agentrun_operation_sdk/cli/__init__.py +1 -0
  2. agentrun_operation_sdk/cli/cli.py +19 -0
  3. agentrun_operation_sdk/cli/common.py +21 -0
  4. agentrun_operation_sdk/cli/runtime/__init__.py +1 -0
  5. agentrun_operation_sdk/cli/runtime/commands.py +203 -0
  6. agentrun_operation_sdk/client/client.py +75 -0
  7. agentrun_operation_sdk/operations/runtime/__init__.py +8 -0
  8. agentrun_operation_sdk/operations/runtime/configure.py +101 -0
  9. agentrun_operation_sdk/operations/runtime/launch.py +82 -0
  10. agentrun_operation_sdk/operations/runtime/models.py +31 -0
  11. agentrun_operation_sdk/services/runtime.py +152 -0
  12. agentrun_operation_sdk/utils/logging_config.py +72 -0
  13. agentrun_operation_sdk/utils/runtime/config.py +94 -0
  14. agentrun_operation_sdk/utils/runtime/container.py +280 -0
  15. agentrun_operation_sdk/utils/runtime/entrypoint.py +203 -0
  16. agentrun_operation_sdk/utils/runtime/schema.py +56 -0
  17. agentrun_sdk/__init__.py +7 -0
  18. agentrun_sdk/agent/__init__.py +25 -0
  19. agentrun_sdk/agent/agent.py +696 -0
  20. agentrun_sdk/agent/agent_result.py +46 -0
  21. agentrun_sdk/agent/conversation_manager/__init__.py +26 -0
  22. agentrun_sdk/agent/conversation_manager/conversation_manager.py +88 -0
  23. agentrun_sdk/agent/conversation_manager/null_conversation_manager.py +46 -0
  24. agentrun_sdk/agent/conversation_manager/sliding_window_conversation_manager.py +179 -0
  25. agentrun_sdk/agent/conversation_manager/summarizing_conversation_manager.py +252 -0
  26. agentrun_sdk/agent/state.py +97 -0
  27. agentrun_sdk/event_loop/__init__.py +9 -0
  28. agentrun_sdk/event_loop/event_loop.py +499 -0
  29. agentrun_sdk/event_loop/streaming.py +319 -0
  30. agentrun_sdk/experimental/__init__.py +4 -0
  31. agentrun_sdk/experimental/hooks/__init__.py +15 -0
  32. agentrun_sdk/experimental/hooks/events.py +123 -0
  33. agentrun_sdk/handlers/__init__.py +10 -0
  34. agentrun_sdk/handlers/callback_handler.py +70 -0
  35. agentrun_sdk/hooks/__init__.py +49 -0
  36. agentrun_sdk/hooks/events.py +80 -0
  37. agentrun_sdk/hooks/registry.py +247 -0
  38. agentrun_sdk/models/__init__.py +10 -0
  39. agentrun_sdk/models/anthropic.py +432 -0
  40. agentrun_sdk/models/bedrock.py +649 -0
  41. agentrun_sdk/models/litellm.py +225 -0
  42. agentrun_sdk/models/llamaapi.py +438 -0
  43. agentrun_sdk/models/mistral.py +539 -0
  44. agentrun_sdk/models/model.py +95 -0
  45. agentrun_sdk/models/ollama.py +357 -0
  46. agentrun_sdk/models/openai.py +436 -0
  47. agentrun_sdk/models/sagemaker.py +598 -0
  48. agentrun_sdk/models/writer.py +449 -0
  49. agentrun_sdk/multiagent/__init__.py +22 -0
  50. agentrun_sdk/multiagent/a2a/__init__.py +15 -0
  51. agentrun_sdk/multiagent/a2a/executor.py +148 -0
  52. agentrun_sdk/multiagent/a2a/server.py +252 -0
  53. agentrun_sdk/multiagent/base.py +92 -0
  54. agentrun_sdk/multiagent/graph.py +555 -0
  55. agentrun_sdk/multiagent/swarm.py +656 -0
  56. agentrun_sdk/py.typed +1 -0
  57. agentrun_sdk/session/__init__.py +18 -0
  58. agentrun_sdk/session/file_session_manager.py +216 -0
  59. agentrun_sdk/session/repository_session_manager.py +152 -0
  60. agentrun_sdk/session/s3_session_manager.py +272 -0
  61. agentrun_sdk/session/session_manager.py +73 -0
  62. agentrun_sdk/session/session_repository.py +51 -0
  63. agentrun_sdk/telemetry/__init__.py +21 -0
  64. agentrun_sdk/telemetry/config.py +194 -0
  65. agentrun_sdk/telemetry/metrics.py +476 -0
  66. agentrun_sdk/telemetry/metrics_constants.py +15 -0
  67. agentrun_sdk/telemetry/tracer.py +563 -0
  68. agentrun_sdk/tools/__init__.py +17 -0
  69. agentrun_sdk/tools/decorator.py +569 -0
  70. agentrun_sdk/tools/executor.py +137 -0
  71. agentrun_sdk/tools/loader.py +152 -0
  72. agentrun_sdk/tools/mcp/__init__.py +13 -0
  73. agentrun_sdk/tools/mcp/mcp_agent_tool.py +99 -0
  74. agentrun_sdk/tools/mcp/mcp_client.py +423 -0
  75. agentrun_sdk/tools/mcp/mcp_instrumentation.py +322 -0
  76. agentrun_sdk/tools/mcp/mcp_types.py +63 -0
  77. agentrun_sdk/tools/registry.py +607 -0
  78. agentrun_sdk/tools/structured_output.py +421 -0
  79. agentrun_sdk/tools/tools.py +217 -0
  80. agentrun_sdk/tools/watcher.py +136 -0
  81. agentrun_sdk/types/__init__.py +5 -0
  82. agentrun_sdk/types/collections.py +23 -0
  83. agentrun_sdk/types/content.py +188 -0
  84. agentrun_sdk/types/event_loop.py +48 -0
  85. agentrun_sdk/types/exceptions.py +81 -0
  86. agentrun_sdk/types/guardrails.py +254 -0
  87. agentrun_sdk/types/media.py +89 -0
  88. agentrun_sdk/types/session.py +152 -0
  89. agentrun_sdk/types/streaming.py +201 -0
  90. agentrun_sdk/types/tools.py +258 -0
  91. agentrun_sdk/types/traces.py +5 -0
  92. agentrun_sdk-0.1.2.dist-info/METADATA +51 -0
  93. agentrun_sdk-0.1.2.dist-info/RECORD +115 -0
  94. agentrun_sdk-0.1.2.dist-info/WHEEL +5 -0
  95. agentrun_sdk-0.1.2.dist-info/entry_points.txt +2 -0
  96. agentrun_sdk-0.1.2.dist-info/top_level.txt +3 -0
  97. agentrun_wrapper/__init__.py +11 -0
  98. agentrun_wrapper/_utils/__init__.py +6 -0
  99. agentrun_wrapper/_utils/endpoints.py +16 -0
  100. agentrun_wrapper/identity/__init__.py +5 -0
  101. agentrun_wrapper/identity/auth.py +211 -0
  102. agentrun_wrapper/memory/__init__.py +6 -0
  103. agentrun_wrapper/memory/client.py +1697 -0
  104. agentrun_wrapper/memory/constants.py +103 -0
  105. agentrun_wrapper/memory/controlplane.py +626 -0
  106. agentrun_wrapper/py.typed +1 -0
  107. agentrun_wrapper/runtime/__init__.py +13 -0
  108. agentrun_wrapper/runtime/app.py +473 -0
  109. agentrun_wrapper/runtime/context.py +34 -0
  110. agentrun_wrapper/runtime/models.py +25 -0
  111. agentrun_wrapper/services/__init__.py +1 -0
  112. agentrun_wrapper/services/identity.py +192 -0
  113. agentrun_wrapper/tools/__init__.py +6 -0
  114. agentrun_wrapper/tools/browser_client.py +325 -0
  115. agentrun_wrapper/tools/code_interpreter_client.py +186 -0
@@ -0,0 +1,626 @@
1
+ """AgentCore Memory SDK - Control Plane Client.
2
+
3
+ This module provides a simplified interface for Bedrock AgentCore Memory control plane operations.
4
+ It handles memory resource management, strategy operations, and status monitoring.
5
+ """
6
+
7
+ import logging
8
+ import os
9
+ import time
10
+ import uuid
11
+ from typing import Any, Dict, List, Optional
12
+
13
+ import boto3
14
+ from botocore.exceptions import ClientError
15
+
16
+ from .constants import (
17
+ MemoryStatus,
18
+ )
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class MemoryControlPlaneClient:
24
+ """Client for Bedrock AgentCore Memory control plane operations."""
25
+
26
+ def __init__(self, region_name: str = "us-west-2", environment: str = "prod"):
27
+ """Initialize the Memory Control Plane client.
28
+
29
+ Args:
30
+ region_name: AWS region name
31
+ environment: Environment name (prod, gamma, etc.)
32
+ """
33
+ self.region_name = region_name
34
+ self.environment = environment
35
+
36
+ self.endpoint = os.getenv(
37
+ "BEDROCK_AGENTCORE_CONTROL_ENDPOINT", f"https://bedrock-agentcore-control.{region_name}.amazonaws.com"
38
+ )
39
+
40
+ service_name = os.getenv("BEDROCK_AGENTCORE_CONTROL_SERVICE", "bedrock-agentcore-control")
41
+ self.client = boto3.client(service_name, region_name=self.region_name, endpoint_url=self.endpoint)
42
+
43
+ logger.info("Initialized MemoryControlPlaneClient for %s in %s", environment, region_name)
44
+
45
+ # ==================== MEMORY OPERATIONS ====================
46
+
47
+ def create_memory(
48
+ self,
49
+ name: str,
50
+ event_expiry_days: int = 90,
51
+ description: Optional[str] = None,
52
+ memory_execution_role_arn: Optional[str] = None,
53
+ strategies: Optional[List[Dict[str, Any]]] = None,
54
+ wait_for_active: bool = False,
55
+ max_wait: int = 300,
56
+ poll_interval: int = 10,
57
+ ) -> Dict[str, Any]:
58
+ """Create a memory resource with optional strategies.
59
+
60
+ Args:
61
+ name: Name for the memory resource
62
+ event_expiry_days: How long to retain events (default: 90 days)
63
+ description: Optional description
64
+ memory_execution_role_arn: IAM role ARN for memory execution
65
+ strategies: Optional list of strategy configurations
66
+ wait_for_active: Whether to wait for memory to become ACTIVE
67
+ max_wait: Maximum seconds to wait if wait_for_active is True
68
+ poll_interval: Seconds between status checks if wait_for_active is True
69
+
70
+ Returns:
71
+ Created memory object
72
+ """
73
+ params = {
74
+ "name": name,
75
+ "eventExpiryDuration": event_expiry_days,
76
+ "clientToken": str(uuid.uuid4()),
77
+ }
78
+
79
+ if description:
80
+ params["description"] = description
81
+
82
+ if memory_execution_role_arn:
83
+ params["memoryExecutionRoleArn"] = memory_execution_role_arn
84
+
85
+ if strategies:
86
+ params["memoryStrategies"] = strategies
87
+
88
+ try:
89
+ response = self.client.create_memory(**params)
90
+ memory = response["memory"]
91
+ memory_id = memory["id"]
92
+
93
+ logger.info("Created memory: %s", memory_id)
94
+
95
+ if wait_for_active:
96
+ return self._wait_for_memory_active(memory_id, max_wait, poll_interval)
97
+
98
+ return memory
99
+
100
+ except ClientError as e:
101
+ logger.error("Failed to create memory: %s", e)
102
+ raise
103
+
104
+ def get_memory(self, memory_id: str, include_strategies: bool = True) -> Dict[str, Any]:
105
+ """Get a memory resource by ID.
106
+
107
+ Args:
108
+ memory_id: Memory resource ID
109
+ include_strategies: Whether to include strategy details in response
110
+
111
+ Returns:
112
+ Memory resource details
113
+ """
114
+ try:
115
+ response = self.client.get_memory(memoryId=memory_id)
116
+ memory = response["memory"]
117
+
118
+ # Add strategy count
119
+ strategies = memory.get("strategies", [])
120
+ memory["strategyCount"] = len(strategies)
121
+
122
+ # Remove strategies if not requested
123
+ if not include_strategies and "strategies" in memory:
124
+ del memory["strategies"]
125
+
126
+ return memory
127
+
128
+ except ClientError as e:
129
+ logger.error("Failed to get memory: %s", e)
130
+ raise
131
+
132
+ def list_memories(self, max_results: int = 100) -> List[Dict[str, Any]]:
133
+ """List all memories for the account with pagination support.
134
+
135
+ Args:
136
+ max_results: Maximum number of memories to return
137
+
138
+ Returns:
139
+ List of memory summaries
140
+ """
141
+ try:
142
+ memories = []
143
+ next_token = None
144
+
145
+ while len(memories) < max_results:
146
+ params = {"maxResults": min(100, max_results - len(memories))}
147
+ if next_token:
148
+ params["nextToken"] = next_token
149
+
150
+ response = self.client.list_memories(**params)
151
+ batch = response.get("memories", [])
152
+ memories.extend(batch)
153
+
154
+ next_token = response.get("nextToken")
155
+ if not next_token or len(memories) >= max_results:
156
+ break
157
+
158
+ # Add strategy count to each memory summary
159
+ for memory in memories:
160
+ memory["strategyCount"] = 0 # List memories doesn't include strategies
161
+
162
+ return memories[:max_results]
163
+
164
+ except ClientError as e:
165
+ logger.error("Failed to list memories: %s", e)
166
+ raise
167
+
168
+ def update_memory(
169
+ self,
170
+ memory_id: str,
171
+ description: Optional[str] = None,
172
+ event_expiry_days: Optional[int] = None,
173
+ memory_execution_role_arn: Optional[str] = None,
174
+ add_strategies: Optional[List[Dict[str, Any]]] = None,
175
+ modify_strategies: Optional[List[Dict[str, Any]]] = None,
176
+ delete_strategy_ids: Optional[List[str]] = None,
177
+ wait_for_active: bool = False,
178
+ max_wait: int = 300,
179
+ poll_interval: int = 10,
180
+ ) -> Dict[str, Any]:
181
+ """Update a memory resource properties and/or strategies.
182
+
183
+ Args:
184
+ memory_id: Memory resource ID
185
+ description: Optional new description
186
+ event_expiry_days: Optional new event expiry duration
187
+ memory_execution_role_arn: Optional new execution role ARN
188
+ add_strategies: Optional list of strategies to add
189
+ modify_strategies: Optional list of strategies to modify
190
+ delete_strategy_ids: Optional list of strategy IDs to delete
191
+ wait_for_active: Whether to wait for memory to become ACTIVE
192
+ max_wait: Maximum seconds to wait if wait_for_active is True
193
+ poll_interval: Seconds between status checks if wait_for_active is True
194
+
195
+ Returns:
196
+ Updated memory object
197
+ """
198
+ params: Dict = {
199
+ "memoryId": memory_id,
200
+ "clientToken": str(uuid.uuid4()),
201
+ }
202
+
203
+ # Add memory properties if provided
204
+ if description is not None:
205
+ params["description"] = description
206
+
207
+ if event_expiry_days is not None:
208
+ params["eventExpiryDuration"] = event_expiry_days
209
+
210
+ if memory_execution_role_arn is not None:
211
+ params["memoryExecutionRoleArn"] = memory_execution_role_arn
212
+
213
+ # Add strategy operations if provided
214
+ memory_strategies = {}
215
+
216
+ if add_strategies:
217
+ memory_strategies["addMemoryStrategies"] = add_strategies
218
+
219
+ if modify_strategies:
220
+ memory_strategies["modifyMemoryStrategies"] = modify_strategies
221
+
222
+ if delete_strategy_ids:
223
+ memory_strategies["deleteMemoryStrategies"] = [
224
+ {"memoryStrategyId": strategy_id} for strategy_id in delete_strategy_ids
225
+ ]
226
+
227
+ if memory_strategies:
228
+ params["memoryStrategies"] = memory_strategies
229
+
230
+ try:
231
+ response = self.client.update_memory(**params)
232
+ memory = response["memory"]
233
+ logger.info("Updated memory: %s", memory_id)
234
+
235
+ if wait_for_active:
236
+ return self._wait_for_memory_active(memory_id, max_wait, poll_interval)
237
+
238
+ return memory
239
+
240
+ except ClientError as e:
241
+ logger.error("Failed to update memory: %s", e)
242
+ raise
243
+
244
+ def delete_memory(
245
+ self,
246
+ memory_id: str,
247
+ wait_for_deletion: bool = False,
248
+ wait_for_strategies: bool = False, # Changed default to False
249
+ max_wait: int = 300,
250
+ poll_interval: int = 10,
251
+ ) -> Dict[str, Any]:
252
+ """Delete a memory resource.
253
+
254
+ Args:
255
+ memory_id: Memory resource ID to delete
256
+ wait_for_deletion: Whether to wait for complete deletion
257
+ wait_for_strategies: Whether to wait for strategies to become ACTIVE before deletion
258
+ max_wait: Maximum seconds to wait if wait_for_deletion is True
259
+ poll_interval: Seconds between checks if wait_for_deletion is True
260
+
261
+ Returns:
262
+ Deletion response
263
+ """
264
+ try:
265
+ # If requested, wait for all strategies to become ACTIVE before deletion
266
+ if wait_for_strategies:
267
+ try:
268
+ memory = self.get_memory(memory_id)
269
+ strategies = memory.get("strategies", [])
270
+
271
+ # Check if any strategies are in a transitional state
272
+ transitional_strategies = [
273
+ s
274
+ for s in strategies
275
+ if s.get("status") not in [MemoryStatus.ACTIVE.value, MemoryStatus.FAILED.value]
276
+ ]
277
+
278
+ if transitional_strategies:
279
+ logger.info(
280
+ "Waiting for %d strategies to become ACTIVE before deletion", len(transitional_strategies)
281
+ )
282
+ self._wait_for_status(
283
+ memory_id=memory_id,
284
+ target_status=MemoryStatus.ACTIVE.value,
285
+ max_wait=max_wait,
286
+ poll_interval=poll_interval,
287
+ check_strategies=True,
288
+ )
289
+ except Exception as e:
290
+ logger.warning("Error waiting for strategies to become ACTIVE: %s", e)
291
+
292
+ # Now delete the memory
293
+ response = self.client.delete_memory(memoryId=memory_id, clientToken=str(uuid.uuid4()))
294
+
295
+ logger.info("Initiated deletion of memory: %s", memory_id)
296
+
297
+ if not wait_for_deletion:
298
+ return response
299
+
300
+ # Wait for deletion to complete
301
+ start_time = time.time()
302
+ while time.time() - start_time < max_wait:
303
+ try:
304
+ self.client.get_memory(memoryId=memory_id)
305
+ time.sleep(poll_interval)
306
+ except ClientError as e:
307
+ if e.response["Error"]["Code"] == "ResourceNotFoundException":
308
+ logger.info("Memory %s successfully deleted", memory_id)
309
+ return response
310
+ raise
311
+
312
+ raise TimeoutError(f"Memory {memory_id} was not deleted within {max_wait} seconds")
313
+
314
+ except ClientError as e:
315
+ logger.error("Failed to delete memory: %s", e)
316
+ raise
317
+
318
+ # ==================== STRATEGY OPERATIONS ====================
319
+
320
+ def add_strategy(
321
+ self,
322
+ memory_id: str,
323
+ strategy: Dict[str, Any],
324
+ wait_for_active: bool = False,
325
+ max_wait: int = 300,
326
+ poll_interval: int = 10,
327
+ ) -> Dict[str, Any]:
328
+ """Add a strategy to a memory resource.
329
+
330
+ Args:
331
+ memory_id: Memory resource ID
332
+ strategy: Strategy configuration dictionary
333
+ wait_for_active: Whether to wait for strategy to become ACTIVE
334
+ max_wait: Maximum seconds to wait if wait_for_active is True
335
+ poll_interval: Seconds between status checks if wait_for_active is True
336
+
337
+ Returns:
338
+ Updated memory object with strategyId field
339
+ """
340
+ # Get the strategy type and name for identification
341
+ strategy_type = list(strategy.keys())[0] # e.g., 'semanticMemoryStrategy'
342
+ strategy_name = strategy[strategy_type].get("name")
343
+
344
+ logger.info("Adding strategy %s of type %s to memory %s", strategy_name, strategy_type, memory_id)
345
+
346
+ # Use update_memory with add_strategies parameter but don't wait for memory
347
+ memory = self.update_memory(
348
+ memory_id=memory_id,
349
+ add_strategies=[strategy],
350
+ wait_for_active=False, # Don't wait for memory, we'll check strategy specifically
351
+ )
352
+
353
+ # If we need to wait for the strategy to become active
354
+ if wait_for_active:
355
+ # First, get the memory again to ensure we have the latest state
356
+ memory = self.get_memory(memory_id)
357
+
358
+ # Find the newly added strategy by matching name
359
+ strategies = memory.get("strategies", [])
360
+ strategy_id = None
361
+
362
+ for s in strategies:
363
+ # Match by name since that's unique within a memory
364
+ if s.get("name") == strategy_name:
365
+ strategy_id = s.get("strategyId")
366
+ logger.info("Found newly added strategy %s with ID %s", strategy_name, strategy_id)
367
+ break
368
+
369
+ if strategy_id:
370
+ return self._wait_for_strategy_active(memory_id, strategy_id, max_wait, poll_interval)
371
+ else:
372
+ logger.warning("Could not identify newly added strategy %s to wait for activation", strategy_name)
373
+
374
+ return memory
375
+
376
+ def get_strategy(self, memory_id: str, strategy_id: str) -> Dict[str, Any]:
377
+ """Get a specific strategy from a memory resource.
378
+
379
+ Args:
380
+ memory_id: Memory resource ID
381
+ strategy_id: Strategy ID
382
+
383
+ Returns:
384
+ Strategy details
385
+ """
386
+ try:
387
+ memory = self.get_memory(memory_id)
388
+ strategies = memory.get("strategies", [])
389
+
390
+ for strategy in strategies:
391
+ if strategy.get("strategyId") == strategy_id:
392
+ return strategy
393
+
394
+ raise ValueError(f"Strategy {strategy_id} not found in memory {memory_id}")
395
+
396
+ except ClientError as e:
397
+ logger.error("Failed to get strategy: %s", e)
398
+ raise
399
+
400
+ def update_strategy(
401
+ self,
402
+ memory_id: str,
403
+ strategy_id: str,
404
+ description: Optional[str] = None,
405
+ namespaces: Optional[List[str]] = None,
406
+ configuration: Optional[Dict[str, Any]] = None,
407
+ wait_for_active: bool = False,
408
+ max_wait: int = 300,
409
+ poll_interval: int = 10,
410
+ ) -> Dict[str, Any]:
411
+ """Update a strategy in a memory resource.
412
+
413
+ Args:
414
+ memory_id: Memory resource ID
415
+ strategy_id: Strategy ID to update
416
+ description: Optional new description
417
+ namespaces: Optional new namespaces list
418
+ configuration: Optional new configuration
419
+ wait_for_active: Whether to wait for strategy to become ACTIVE
420
+ max_wait: Maximum seconds to wait if wait_for_active is True
421
+ poll_interval: Seconds between status checks if wait_for_active is True
422
+
423
+ Returns:
424
+ Updated memory object
425
+ """
426
+ # Note: API expects memoryStrategyId for input but returns strategyId in response
427
+ modify_config: Dict = {"memoryStrategyId": strategy_id}
428
+
429
+ if description is not None:
430
+ modify_config["description"] = description
431
+
432
+ if namespaces is not None:
433
+ modify_config["namespaces"] = namespaces
434
+
435
+ if configuration is not None:
436
+ modify_config["configuration"] = configuration
437
+
438
+ # Use update_memory with modify_strategies parameter but don't wait for memory
439
+ memory = self.update_memory(
440
+ memory_id=memory_id,
441
+ modify_strategies=[modify_config],
442
+ wait_for_active=False, # Don't wait for memory, we'll check strategy specifically
443
+ )
444
+
445
+ # If we need to wait for the strategy to become active
446
+ if wait_for_active:
447
+ return self._wait_for_strategy_active(memory_id, strategy_id, max_wait, poll_interval)
448
+
449
+ return memory
450
+
451
+ def remove_strategy(
452
+ self,
453
+ memory_id: str,
454
+ strategy_id: str,
455
+ wait_for_active: bool = False,
456
+ max_wait: int = 300,
457
+ poll_interval: int = 10,
458
+ ) -> Dict[str, Any]:
459
+ """Remove a strategy from a memory resource.
460
+
461
+ Args:
462
+ memory_id: Memory resource ID
463
+ strategy_id: Strategy ID to remove
464
+ wait_for_active: Whether to wait for memory to become ACTIVE
465
+ max_wait: Maximum seconds to wait if wait_for_active is True
466
+ poll_interval: Seconds between status checks if wait_for_active is True
467
+
468
+ Returns:
469
+ Updated memory object
470
+ """
471
+ # For remove_strategy, we only need to wait for memory to be active
472
+ # since the strategy will be gone
473
+ return self.update_memory(
474
+ memory_id=memory_id,
475
+ delete_strategy_ids=[strategy_id],
476
+ wait_for_active=wait_for_active,
477
+ max_wait=max_wait,
478
+ poll_interval=poll_interval,
479
+ )
480
+
481
+ # ==================== HELPER METHODS ====================
482
+
483
+ def _wait_for_memory_active(self, memory_id: str, max_wait: int, poll_interval: int) -> Dict[str, Any]:
484
+ """Wait for memory to return to ACTIVE state."""
485
+ logger.info("Waiting for memory %s to become ACTIVE...", memory_id)
486
+ return self._wait_for_status(
487
+ memory_id=memory_id, target_status=MemoryStatus.ACTIVE.value, max_wait=max_wait, poll_interval=poll_interval
488
+ )
489
+
490
+ def _wait_for_strategy_active(
491
+ self, memory_id: str, strategy_id: str, max_wait: int, poll_interval: int
492
+ ) -> Dict[str, Any]:
493
+ """Wait for specific memory strategy to become ACTIVE."""
494
+ logger.info("Waiting for strategy %s to become ACTIVE (max wait: %d seconds)...", strategy_id, max_wait)
495
+
496
+ start_time = time.time()
497
+ last_status = None
498
+
499
+ while time.time() - start_time < max_wait:
500
+ try:
501
+ memory = self.get_memory(memory_id)
502
+ strategies = memory.get("strategies", [])
503
+
504
+ for strategy in strategies:
505
+ if strategy.get("strategyId") == strategy_id:
506
+ status = strategy["status"]
507
+
508
+ # Log status changes
509
+ if status != last_status:
510
+ logger.info("Strategy %s status: %s", strategy_id, status)
511
+ last_status = status
512
+
513
+ if status == MemoryStatus.ACTIVE.value:
514
+ elapsed = time.time() - start_time
515
+ logger.info("Strategy %s is now ACTIVE (took %.1f seconds)", strategy_id, elapsed)
516
+ return memory
517
+ elif status == MemoryStatus.FAILED.value:
518
+ failure_reason = strategy.get("failureReason", "Unknown")
519
+ raise RuntimeError(f"Strategy {strategy_id} failed to activate: {failure_reason}")
520
+
521
+ break
522
+ else:
523
+ logger.warning("Strategy %s not found in memory %s", strategy_id, memory_id)
524
+
525
+ # Wait before checking again
526
+ time.sleep(poll_interval)
527
+
528
+ except ClientError as e:
529
+ logger.error("Error checking strategy status: %s", e)
530
+ raise
531
+
532
+ elapsed = time.time() - start_time
533
+ raise TimeoutError(
534
+ f"Strategy {strategy_id} did not become ACTIVE within {max_wait} seconds (last status: {last_status})"
535
+ )
536
+
537
+ def _wait_for_status(
538
+ self, memory_id: str, target_status: str, max_wait: int, poll_interval: int, check_strategies: bool = True
539
+ ) -> Dict[str, Any]:
540
+ """Generic method to wait for a memory to reach a specific status.
541
+
542
+ Args:
543
+ memory_id: The ID of the memory to check
544
+ target_status: The status to wait for (e.g., "ACTIVE")
545
+ max_wait: Maximum time to wait in seconds
546
+ poll_interval: Time between status checks in seconds
547
+ check_strategies: Whether to also check that all strategies are in the target status
548
+
549
+ Returns:
550
+ The memory object once it reaches the target status
551
+
552
+ Raises:
553
+ TimeoutError: If the memory doesn't reach the target status within max_wait
554
+ RuntimeError: If the memory or any strategy reaches a FAILED state
555
+ """
556
+ logger.info("Waiting for memory %s to reach status %s...", memory_id, target_status)
557
+
558
+ start_time = time.time()
559
+ last_memory_status = None
560
+ strategy_statuses = {}
561
+
562
+ while time.time() - start_time < max_wait:
563
+ try:
564
+ memory = self.get_memory(memory_id)
565
+ status = memory.get("status")
566
+
567
+ # Log status changes for memory
568
+ if status != last_memory_status:
569
+ logger.info("Memory %s status: %s", memory_id, status)
570
+ last_memory_status = status
571
+
572
+ if status == target_status:
573
+ # Check if all strategies are also in the target status
574
+ if check_strategies and target_status == MemoryStatus.ACTIVE.value:
575
+ strategies = memory.get("strategies", [])
576
+ all_strategies_active = True
577
+
578
+ for strategy in strategies:
579
+ strategy_id = strategy.get("strategyId")
580
+ strategy_status = strategy.get("status")
581
+
582
+ # Log strategy status changes
583
+ if (
584
+ strategy_id not in strategy_statuses
585
+ or strategy_statuses[strategy_id] != strategy_status
586
+ ):
587
+ logger.info("Strategy %s status: %s", strategy_id, strategy_status)
588
+ strategy_statuses[strategy_id] = strategy_status
589
+
590
+ if strategy_status != target_status:
591
+ if strategy_status == MemoryStatus.FAILED.value:
592
+ failure_reason = strategy.get("failureReason", "Unknown")
593
+ raise RuntimeError(f"Strategy {strategy_id} failed: {failure_reason}")
594
+
595
+ all_strategies_active = False
596
+
597
+ if not all_strategies_active:
598
+ logger.info(
599
+ "Memory %s is %s but %d strategies are still processing",
600
+ memory_id,
601
+ target_status,
602
+ len([s for s in strategies if s.get("status") != target_status]),
603
+ )
604
+ time.sleep(poll_interval)
605
+ continue
606
+
607
+ elapsed = time.time() - start_time
608
+ logger.info(
609
+ "Memory %s and all strategies are now %s (took %.1f seconds)", memory_id, target_status, elapsed
610
+ )
611
+ return memory
612
+ elif status == MemoryStatus.FAILED.value:
613
+ failure_reason = memory.get("failureReason", "Unknown")
614
+ raise RuntimeError(f"Memory operation failed: {failure_reason}")
615
+
616
+ time.sleep(poll_interval)
617
+
618
+ except ClientError as e:
619
+ logger.error("Error checking memory status: %s", e)
620
+ raise
621
+
622
+ elapsed = time.time() - start_time
623
+ raise TimeoutError(
624
+ f"Memory {memory_id} did not reach status {target_status} within {max_wait} seconds "
625
+ f"(elapsed: {elapsed:.1f}s)"
626
+ )
@@ -0,0 +1 @@
1
+ # Marker file that indicates this package supports typing
@@ -0,0 +1,13 @@
1
+ """BedrockAgentCore Runtime Package.
2
+
3
+ This package contains the core runtime components for Bedrock AgentCore applications:
4
+ - BedrockAgentCoreApp: Main application class
5
+ - RequestContext: HTTP request context
6
+ - BedrockAgentCoreContext: Agent identity context
7
+ """
8
+
9
+ from .app import BedrockAgentCoreApp
10
+ from .context import BedrockAgentCoreContext, RequestContext
11
+ from .models import PingStatus
12
+
13
+ __all__ = ["BedrockAgentCoreApp", "RequestContext", "BedrockAgentCoreContext", "PingStatus"]