bedrock-agentcore-starter-toolkit 0.1.21__py3-none-any.whl → 0.1.23__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 bedrock-agentcore-starter-toolkit might be problematic. Click here for more details.

Files changed (25) hide show
  1. bedrock_agentcore_starter_toolkit/cli/common.py +1 -1
  2. bedrock_agentcore_starter_toolkit/cli/runtime/commands.py +167 -51
  3. bedrock_agentcore_starter_toolkit/cli/runtime/configuration_manager.py +45 -17
  4. bedrock_agentcore_starter_toolkit/notebook/runtime/bedrock_agentcore.py +30 -1
  5. bedrock_agentcore_starter_toolkit/operations/memory/manager.py +1 -1
  6. bedrock_agentcore_starter_toolkit/operations/runtime/__init__.py +12 -1
  7. bedrock_agentcore_starter_toolkit/operations/runtime/configure.py +182 -29
  8. bedrock_agentcore_starter_toolkit/operations/runtime/destroy.py +24 -7
  9. bedrock_agentcore_starter_toolkit/operations/runtime/exceptions.py +27 -0
  10. bedrock_agentcore_starter_toolkit/operations/runtime/invoke.py +12 -3
  11. bedrock_agentcore_starter_toolkit/operations/runtime/launch.py +99 -44
  12. bedrock_agentcore_starter_toolkit/operations/runtime/status.py +5 -4
  13. bedrock_agentcore_starter_toolkit/services/codebuild.py +53 -26
  14. bedrock_agentcore_starter_toolkit/services/ecr.py +44 -2
  15. bedrock_agentcore_starter_toolkit/utils/runtime/config.py +43 -1
  16. bedrock_agentcore_starter_toolkit/utils/runtime/container.py +89 -30
  17. bedrock_agentcore_starter_toolkit/utils/runtime/schema.py +43 -4
  18. bedrock_agentcore_starter_toolkit/utils/runtime/templates/Dockerfile.j2 +1 -6
  19. bedrock_agentcore_starter_toolkit/utils/runtime/templates/dockerignore.template +1 -0
  20. {bedrock_agentcore_starter_toolkit-0.1.21.dist-info → bedrock_agentcore_starter_toolkit-0.1.23.dist-info}/METADATA +2 -2
  21. {bedrock_agentcore_starter_toolkit-0.1.21.dist-info → bedrock_agentcore_starter_toolkit-0.1.23.dist-info}/RECORD +25 -24
  22. {bedrock_agentcore_starter_toolkit-0.1.21.dist-info → bedrock_agentcore_starter_toolkit-0.1.23.dist-info}/WHEEL +0 -0
  23. {bedrock_agentcore_starter_toolkit-0.1.21.dist-info → bedrock_agentcore_starter_toolkit-0.1.23.dist-info}/entry_points.txt +0 -0
  24. {bedrock_agentcore_starter_toolkit-0.1.21.dist-info → bedrock_agentcore_starter_toolkit-0.1.23.dist-info}/licenses/LICENSE.txt +0 -0
  25. {bedrock_agentcore_starter_toolkit-0.1.21.dist-info → bedrock_agentcore_starter_toolkit-0.1.23.dist-info}/licenses/NOTICE.txt +0 -0
@@ -20,7 +20,7 @@ def _handle_error(message: str, exception: Optional[Exception] = None) -> NoRetu
20
20
 
21
21
  def _handle_warn(message: str) -> None:
22
22
  """Handle errors with consistent formatting and exit."""
23
- console.print(f"⚠️ {message}", new_line_start=True, style="bold yellow underline")
23
+ console.print(f"⚠️ {message}", new_line_start=True, style="yellow")
24
24
 
25
25
 
26
26
  def _print_success(message: str) -> None:
@@ -15,13 +15,16 @@ from rich.syntax import Syntax
15
15
  from ...operations.runtime import (
16
16
  configure_bedrock_agentcore,
17
17
  destroy_bedrock_agentcore,
18
+ detect_entrypoint,
19
+ detect_requirements,
20
+ get_relative_path,
18
21
  get_status,
22
+ infer_agent_name,
19
23
  invoke_bedrock_agentcore,
20
24
  launch_bedrock_agentcore,
21
25
  validate_agent_name,
22
26
  )
23
27
  from ...utils.runtime.config import load_config
24
- from ...utils.runtime.entrypoint import parse_entrypoint
25
28
  from ...utils.runtime.logs import get_agent_log_paths, get_aws_tail_commands, get_genai_observability_url
26
29
  from ..common import _handle_error, _print_success, console
27
30
  from .configuration_manager import ConfigurationManager
@@ -47,63 +50,102 @@ def _show_configuration_not_found_panel():
47
50
 
48
51
 
49
52
  def _validate_requirements_file(file_path: str) -> str:
50
- """Validate requirements file and return the path."""
53
+ """Validate requirements file and return the absolute path."""
51
54
  from ...utils.runtime.entrypoint import validate_requirements_file
52
55
 
53
56
  try:
54
57
  deps = validate_requirements_file(Path.cwd(), file_path)
55
- _print_success(f"Using requirements file: [dim]{deps.resolved_path}[/dim]")
56
- return file_path
58
+ rel_path = get_relative_path(Path(deps.resolved_path))
59
+ _print_success(f"Using requirements file: [dim]{rel_path}[/dim]")
60
+ # Return absolute path for consistency with entrypoint handling
61
+ return str(Path(deps.resolved_path).resolve())
57
62
  except (FileNotFoundError, ValueError) as e:
58
63
  _handle_error(str(e), e)
59
64
 
60
65
 
61
- def _prompt_for_requirements_file(prompt_text: str, default: str = "") -> Optional[str]:
62
- """Prompt user for requirements file path with validation."""
63
- response = prompt(prompt_text, completer=PathCompleter(), default=default)
66
+ def _prompt_for_requirements_file(prompt_text: str, source_path: str, default: str = "") -> Optional[str]:
67
+ """Prompt user for requirements file path with validation.
68
+
69
+ Args:
70
+ prompt_text: Prompt message to display
71
+ source_path: Source directory path for validation
72
+ default: Default path to pre-populate
73
+ """
74
+ # Pre-populate with relative source directory path if no default provided
75
+ if not default:
76
+ rel_source = get_relative_path(Path(source_path))
77
+ default = f"{rel_source}/"
78
+
79
+ # Use PathCompleter without filter - allow navigation anywhere
80
+ response = prompt(prompt_text, completer=PathCompleter(), complete_while_typing=True, default=default)
64
81
 
65
82
  if response.strip():
83
+ # Validate file exists and is in source directory
84
+ req_file = Path(response.strip()).resolve()
85
+ source_dir = Path(source_path).resolve()
86
+
87
+ # Check if requirements file is within source directory
88
+ try:
89
+ if not req_file.is_relative_to(source_dir):
90
+ rel_source = get_relative_path(source_dir)
91
+ console.print(f"[red]Error: Requirements file must be in source directory: {rel_source}[/red]")
92
+ return _prompt_for_requirements_file(prompt_text, source_path, default)
93
+ except (ValueError, AttributeError):
94
+ # is_relative_to not available or other error - skip validation
95
+ pass
96
+
66
97
  return _validate_requirements_file(response.strip())
67
98
 
68
99
  return None
69
100
 
70
101
 
71
- def _handle_requirements_file_display(requirements_file: Optional[str], non_interactive: bool = False) -> Optional[str]:
72
- """Handle requirements file with display logic for CLI."""
73
- from ...utils.runtime.entrypoint import detect_dependencies
102
+ def _handle_requirements_file_display(
103
+ requirements_file: Optional[str], non_interactive: bool = False, source_path: Optional[str] = None
104
+ ) -> Optional[str]:
105
+ """Handle requirements file with display logic for CLI.
74
106
 
107
+ Args:
108
+ requirements_file: Explicit requirements file path
109
+ non_interactive: Whether to skip interactive prompts
110
+ source_path: Optional source code directory
111
+ """
75
112
  if requirements_file:
76
113
  # User provided file - validate and show confirmation
77
114
  return _validate_requirements_file(requirements_file)
78
115
 
116
+ # Use operations layer for detection - source_path is always provided
117
+ deps = detect_requirements(Path(source_path))
118
+
79
119
  if non_interactive:
80
120
  # Auto-detection for non-interactive mode
81
- deps = detect_dependencies(Path.cwd())
82
121
  if deps.found:
83
- _print_success(f"Using detected file: [dim]{deps.file}[/dim]")
122
+ rel_deps_path = get_relative_path(Path(deps.resolved_path))
123
+ _print_success(f"Using detected requirements file: [cyan]{rel_deps_path}[/cyan]")
84
124
  return None # Use detected file
85
125
  else:
86
126
  _handle_error("No requirements file specified and none found automatically")
87
127
 
88
128
  # Auto-detection with interactive prompt
89
- deps = detect_dependencies(Path.cwd())
90
-
91
129
  if deps.found:
92
- console.print(f"\n🔍 [cyan]Detected dependency file:[/cyan] [bold]{deps.file}[/bold]")
130
+ rel_deps_path = get_relative_path(Path(deps.resolved_path))
131
+
132
+ console.print(f"\n🔍 [cyan]Detected dependency file:[/cyan] [bold]{rel_deps_path}[/bold]")
93
133
  console.print("[dim]Press Enter to use this file, or type a different path (use Tab for autocomplete):[/dim]")
94
134
 
95
- result = _prompt_for_requirements_file("Path or Press Enter to use detected dependency file: ", default="")
135
+ result = _prompt_for_requirements_file(
136
+ "Path or Press Enter to use detected dependency file: ", source_path=source_path, default=rel_deps_path
137
+ )
96
138
 
97
139
  if result is None:
98
140
  # Use detected file
99
- _print_success(f"Using detected file: [dim]{deps.file}[/dim]")
141
+ _print_success(f"Using detected requirements file: [cyan]{rel_deps_path}[/cyan]")
100
142
 
101
143
  return result
102
144
  else:
103
145
  console.print("\n[yellow]⚠️ No dependency file found (requirements.txt or pyproject.toml)[/yellow]")
104
146
  console.print("[dim]Enter path to requirements file (use Tab for autocomplete), or press Enter to skip:[/dim]")
105
147
 
106
- result = _prompt_for_requirements_file("Path: ")
148
+ result = _prompt_for_requirements_file("Path: ", source_path=source_path)
107
149
 
108
150
  if result is None:
109
151
  _handle_error("No requirements file specified and none found automatically")
@@ -111,6 +153,29 @@ def _handle_requirements_file_display(requirements_file: Optional[str], non_inte
111
153
  return result
112
154
 
113
155
 
156
+ def _detect_entrypoint_in_source(source_path: str, non_interactive: bool = False) -> str:
157
+ """Detect entrypoint file in source directory with CLI display."""
158
+ source_dir = Path(source_path)
159
+
160
+ # Use operations layer for detection
161
+ detected = detect_entrypoint(source_dir)
162
+
163
+ if not detected:
164
+ # No fallback prompt - fail with clear error message
165
+ rel_source = get_relative_path(source_dir)
166
+ _handle_error(
167
+ f"No entrypoint file found in {rel_source}\n"
168
+ f"Expected one of: main.py, agent.py, app.py, __main__.py\n"
169
+ f"Please specify full file path (e.g., {rel_source}/your_agent.py)"
170
+ )
171
+
172
+ # Show detection and confirm
173
+ rel_entrypoint = get_relative_path(detected)
174
+
175
+ _print_success(f"Using entrypoint file: [cyan]{rel_entrypoint}[/cyan]")
176
+ return str(detected)
177
+
178
+
114
179
  # Define options at module level to avoid B008
115
180
  ENV_OPTION = typer.Option(None, "--env", "-env", help="Environment variables for local mode (format: KEY=VALUE)")
116
181
 
@@ -164,7 +229,12 @@ def set_default(name: str = typer.Argument(...)):
164
229
  @configure_app.callback(invoke_without_command=True)
165
230
  def configure(
166
231
  ctx: typer.Context,
167
- entrypoint: Optional[str] = typer.Option(None, "--entrypoint", "-e", help="Python file with BedrockAgentCoreApp"),
232
+ entrypoint: Optional[str] = typer.Option(
233
+ None,
234
+ "--entrypoint",
235
+ "-e",
236
+ help="Entry point: file path (e.g., agent.py) or directory path (auto-detects main.py, agent.py, app.py)",
237
+ ),
168
238
  agent_name: Optional[str] = typer.Option(None, "--name", "-n"),
169
239
  execution_role: Optional[str] = typer.Option(None, "--execution-role", "-er"),
170
240
  code_build_execution_role: Optional[str] = typer.Option(None, "--code-build-execution-role", "-cber"),
@@ -174,6 +244,7 @@ def configure(
174
244
  None, "--requirements-file", "-rf", help="Path to requirements file"
175
245
  ),
176
246
  disable_otel: bool = typer.Option(False, "--disable-otel", "-do", help="Disable OpenTelemetry"),
247
+ disable_memory: bool = typer.Option(False, "--disable-memory", "-dm", help="Disable memory"),
177
248
  authorizer_config: Optional[str] = typer.Option(
178
249
  None, "--authorizer-config", "-ac", help="OAuth authorizer configuration as JSON string"
179
250
  ),
@@ -191,33 +262,74 @@ def configure(
191
262
  False, "--non-interactive", "-ni", help="Skip prompts; use defaults unless overridden"
192
263
  ),
193
264
  ):
194
- """Configure a Bedrock AgentCore agent. The agent name defaults to your Python file name."""
265
+ """Configure a Bedrock AgentCore agent interactively or with parameters.
266
+
267
+ Examples:
268
+ agentcore configure # Fully interactive (current directory)
269
+ agentcore configure --entrypoint writer/ # Directory (auto-detect entrypoint)
270
+ agentcore configure --entrypoint agent.py # File (use as entrypoint)
271
+ """
195
272
  if ctx.invoked_subcommand is not None:
196
273
  return
197
274
 
198
- if not entrypoint:
199
- _handle_error("--entrypoint is required")
200
-
201
275
  if protocol and protocol.upper() not in ["HTTP", "MCP", "A2A"]:
202
276
  _handle_error("Error: --protocol must be either HTTP or MCP or A2A")
203
277
 
204
278
  console.print("[cyan]Configuring Bedrock AgentCore...[/cyan]")
205
- try:
206
- _, file_name = parse_entrypoint(entrypoint)
207
- agent_name = agent_name or file_name
208
-
209
- valid, error = validate_agent_name(agent_name)
210
- if not valid:
211
- _handle_error(error)
212
-
213
- console.print(f"[dim]Agent name: {agent_name}[/dim]")
214
- except ValueError as e:
215
- _handle_error(f"Error: {e}", e)
216
279
 
217
- # Create configuration manager for clean, elegant prompting
280
+ # Create configuration manager early for consistent prompting
218
281
  config_path = Path.cwd() / ".bedrock_agentcore.yaml"
219
282
  config_manager = ConfigurationManager(config_path, non_interactive)
220
283
 
284
+ # Interactive entrypoint selection
285
+ if not entrypoint:
286
+ if non_interactive:
287
+ entrypoint_input = "."
288
+ else:
289
+ console.print("\n📂 [cyan]Entrypoint Selection[/cyan]")
290
+ console.print("[dim]Specify the entry point (use Tab for autocomplete):[/dim]")
291
+ console.print("[dim] • File path: weather/agent.py[/dim]")
292
+ console.print("[dim] • Directory: weather/ (auto-detects main.py, agent.py, app.py)[/dim]")
293
+ console.print("[dim] • Current directory: press Enter[/dim]")
294
+
295
+ entrypoint_input = (
296
+ prompt("Entrypoint: ", completer=PathCompleter(), complete_while_typing=True, default="").strip() or "."
297
+ )
298
+ else:
299
+ entrypoint_input = entrypoint
300
+
301
+ # Resolve the entrypoint_input (handles both file and directory)
302
+ entrypoint_path = Path(entrypoint_input).resolve()
303
+
304
+ if entrypoint_path.is_file():
305
+ # It's a file - use directly as entrypoint
306
+ entrypoint = str(entrypoint_path)
307
+ source_path = str(entrypoint_path.parent)
308
+ if not non_interactive:
309
+ rel_path = get_relative_path(entrypoint_path)
310
+ _print_success(f"Using file: {rel_path}")
311
+ elif entrypoint_path.is_dir():
312
+ # It's a directory - detect entrypoint within it
313
+ source_path = str(entrypoint_path)
314
+ entrypoint = _detect_entrypoint_in_source(source_path, non_interactive)
315
+ else:
316
+ _handle_error(f"Path not found: {entrypoint_input}")
317
+
318
+ # Process agent name
319
+ entrypoint_path = Path(entrypoint)
320
+
321
+ # Infer agent name from full entrypoint path (e.g., agents/writer/main.py -> agents_writer_main)
322
+ if not agent_name:
323
+ suggested_name = infer_agent_name(entrypoint_path)
324
+ agent_name = config_manager.prompt_agent_name(suggested_name)
325
+
326
+ valid, error = validate_agent_name(agent_name)
327
+ if not valid:
328
+ _handle_error(error)
329
+
330
+ # Handle dependency file selection with simplified logic
331
+ final_requirements_file = _handle_requirements_file_display(requirements_file, non_interactive, source_path)
332
+
221
333
  # Interactive prompts for missing values - clean and elegant
222
334
  if not execution_role:
223
335
  execution_role = config_manager.prompt_execution_role()
@@ -236,9 +348,6 @@ def configure(
236
348
  auto_create_ecr = False
237
349
  _print_success(f"Using existing ECR repository: [dim]{ecr_repository}[/dim]")
238
350
 
239
- # Handle dependency file selection with simplified logic
240
- final_requirements_file = _handle_requirements_file_display(requirements_file, non_interactive)
241
-
242
351
  # Handle OAuth authorization configuration
243
352
  oauth_config = None
244
353
  if authorizer_config:
@@ -264,6 +373,11 @@ def configure(
264
373
  else:
265
374
  request_header_config = config_manager.prompt_request_header_allowlist()
266
375
 
376
+ if disable_memory:
377
+ memory_mode_value = "NO_MEMORY"
378
+ else:
379
+ memory_mode_value = "STM_ONLY"
380
+
267
381
  try:
268
382
  result = configure_bedrock_agentcore(
269
383
  agent_name=agent_name,
@@ -274,6 +388,7 @@ def configure(
274
388
  container_runtime=container_runtime,
275
389
  auto_create_ecr=auto_create_ecr,
276
390
  enable_observability=not disable_otel,
391
+ memory_mode=memory_mode_value,
277
392
  requirements_file=final_requirements_file,
278
393
  authorizer_configuration=oauth_config,
279
394
  request_header_configuration=request_header_config,
@@ -281,6 +396,7 @@ def configure(
281
396
  region=region,
282
397
  protocol=protocol.upper() if protocol else None,
283
398
  non_interactive=non_interactive,
399
+ source_path=source_path,
284
400
  )
285
401
 
286
402
  # Prepare authorization info for summary
@@ -294,22 +410,26 @@ def configure(
294
410
  headers = request_header_config.get("requestHeaderAllowlist", [])
295
411
  headers_info = f"Request Headers Allowlist: [dim]{len(headers)} headers configured[/dim]\n"
296
412
 
413
+ execution_role_display = "Auto-create" if not result.execution_role else result.execution_role
414
+ memory_info = "Short-term memory (30-day retention)"
415
+ if disable_memory:
416
+ memory_info = "Disabled"
417
+
297
418
  console.print(
298
419
  Panel(
299
- f"[green]Configuration Complete[/green]\n\n"
300
- f"[bold]Agent Details:[/bold]\n"
420
+ f"[bold]Agent Details[/bold]\n"
301
421
  f"Agent Name: [cyan]{agent_name}[/cyan]\n"
302
422
  f"Runtime: [cyan]{result.runtime}[/cyan]\n"
303
423
  f"Region: [cyan]{result.region}[/cyan]\n"
304
- f"Account: [dim]{result.account_id}[/dim]\n\n"
305
- f"[bold]Configuration:[/bold]\n"
306
- f"Execution Role: [dim]{result.execution_role}[/dim]\n"
307
- f"ECR Repository: [dim]"
424
+ f"Account: [cyan]{result.account_id}[/cyan]\n\n"
425
+ f"[bold]Configuration[/bold]\n"
426
+ f"Execution Role: [cyan]{execution_role_display}[/cyan]\n"
427
+ f"ECR Repository: [cyan]"
308
428
  f"{'Auto-create' if result.auto_create_ecr else result.ecr_repository or 'N/A'}"
309
- f"[/dim]\n"
310
- f"Authorization: [dim]{auth_info}[/dim]\n\n"
429
+ f"[/cyan]\n"
430
+ f"Authorization: [cyan]{auth_info}[/cyan]\n\n"
311
431
  f"{headers_info}\n"
312
- f"Memory: [dim]Short-term memory (30-day retention)[/dim]\n\n"
432
+ f"Memory: [cyan]{memory_info}[/cyan]\n\n"
313
433
  f"📄 Config saved to: [dim]{result.config_path}[/dim]\n\n"
314
434
  f"[bold]Next Steps:[/bold]\n"
315
435
  f" [cyan]agentcore launch[/cyan]",
@@ -473,7 +593,6 @@ def launch(
473
593
  region = agent_config.aws.region if agent_config else "us-east-1"
474
594
 
475
595
  deploy_panel = (
476
- f"✅ [green]CodeBuild Deployment Successful![/green]\n\n"
477
596
  f"[bold]Agent Details:[/bold]\n"
478
597
  f"Agent Name: [cyan]{agent_name}[/cyan]\n"
479
598
  f"Agent ARN: [cyan]{result.agent_arn}[/cyan]\n"
@@ -513,15 +632,12 @@ def launch(
513
632
 
514
633
  if local_build:
515
634
  title = "Local Build Success"
516
- deployment_type = "✅ [green]Local Build Deployment Successful![/green]"
517
635
  icon = "🔧"
518
636
  else:
519
637
  title = "Deployment Success"
520
- deployment_type = "✅ [green]Deployment Successful![/green]"
521
638
  icon = "🚀"
522
639
 
523
640
  deploy_panel = (
524
- f"{deployment_type}\n\n"
525
641
  f"[bold]Agent Details:[/bold]\n"
526
642
  f"Agent Name: [cyan]{agent_name}[/cyan]\n"
527
643
  f"Agent ARN: [cyan]{result.agent_arn}[/cyan]\n"
@@ -10,18 +10,43 @@ from ..common import _handle_error, _print_success, _prompt_with_default, consol
10
10
  class ConfigurationManager:
11
11
  """Manages interactive configuration prompts with existing configuration defaults."""
12
12
 
13
- def __init__(self, config_path: Path, non_interactive: bool = False):
13
+ def __init__(self, config_path: Path, non_interactive: bool = False, region: Optional[str] = None):
14
14
  """Initialize the ConfigPrompt with a configuration path.
15
15
 
16
16
  Args:
17
17
  config_path: Path to the configuration file
18
18
  non_interactive: If True, use defaults without prompting
19
+ region: AWS region for checking existing memories (optional, from configure operation)
19
20
  """
20
21
  from ...utils.runtime.config import load_config_if_exists
21
22
 
22
23
  project_config = load_config_if_exists(config_path)
23
24
  self.existing_config = project_config.get_agent_config() if project_config else None
24
25
  self.non_interactive = non_interactive
26
+ self.region = region
27
+
28
+ def prompt_agent_name(self, suggested_name: str) -> str:
29
+ """Prompt for agent name with a suggested default.
30
+
31
+ Args:
32
+ suggested_name: The suggested agent name based on entrypoint path
33
+
34
+ Returns:
35
+ The selected or entered agent name
36
+ """
37
+ if self.non_interactive:
38
+ _print_success(f"Agent name (inferred): {suggested_name}")
39
+ return suggested_name
40
+
41
+ console.print(f"\n🏷️ [cyan]Inferred agent name[/cyan]: {suggested_name}")
42
+ console.print("[dim]Press Enter to use this name, or type a different one (alphanumeric without '-')[/dim]")
43
+ agent_name = _prompt_with_default("Agent name", suggested_name)
44
+
45
+ if not agent_name:
46
+ agent_name = suggested_name
47
+
48
+ _print_success(f"Using agent name: [cyan]{agent_name}[/cyan]")
49
+ return agent_name
25
50
 
26
51
  def prompt_execution_role(self) -> Optional[str]:
27
52
  """Prompt for execution role. Returns role name/ARN or None for auto-creation."""
@@ -205,7 +230,7 @@ class ConfigurationManager:
205
230
  Returns:
206
231
  Tuple of (enable_memory, enable_ltm)
207
232
  """
208
- console.print("\n🧠 [cyan]Memory Configuration[/cyan]")
233
+ console.print("\n[cyan]Memory Configuration[/cyan]")
209
234
  console.print("Short-term memory stores conversation within sessions.")
210
235
  console.print("Long-term memory extracts preferences and facts across sessions.")
211
236
  console.print()
@@ -238,7 +263,7 @@ class ConfigurationManager:
238
263
  return enable_memory, enable_ltm
239
264
 
240
265
  def prompt_memory_selection(self) -> Tuple[str, str]:
241
- """Prompt user to select existing memory or create new.
266
+ """Prompt user to select existing memory or create new (no skip option).
242
267
 
243
268
  Returns:
244
269
  Tuple of (action, value) where:
@@ -249,23 +274,26 @@ class ConfigurationManager:
249
274
  # In non-interactive mode, default to creating new STM
250
275
  return ("CREATE_NEW", "STM_ONLY")
251
276
 
252
- console.print("\n🧠 [cyan]Memory Configuration[/cyan]")
277
+ console.print("\n[cyan]Memory Configuration[/cyan]")
278
+ console.print("[dim]Tip: Use --disable-memory flag to skip memory entirely[/dim]\n")
253
279
 
254
280
  # Try to list existing memories
255
281
  try:
256
282
  from ...operations.memory.manager import MemoryManager
257
283
 
258
- # Need region - will be passed from configure.py
259
- region = self.existing_config.aws.region if self.existing_config else None
284
+ # Get region from passed parameter OR existing config
285
+ region = self.region or (self.existing_config.aws.region if self.existing_config else None)
286
+
260
287
  if not region:
261
- # Fall back to new memory creation if no region
288
+ # No region available - skip to new memory creation
289
+ console.print("[dim]No region configured yet, proceeding with new memory creation[/dim]")
262
290
  return self._prompt_new_memory_config()
263
291
 
264
292
  memory_manager = MemoryManager(region_name=region)
265
293
  existing_memories = memory_manager.list_memories(max_results=10)
266
294
 
267
295
  if existing_memories:
268
- console.print("\n[cyan]Existing memory resources found:[/cyan]")
296
+ console.print("[cyan]Existing memory resources found:[/cyan]")
269
297
  for i, mem in enumerate(existing_memories, 1):
270
298
  # Display memory summary
271
299
  mem_id = mem.get("id", "unknown")
@@ -283,7 +311,6 @@ class ConfigurationManager:
283
311
  console.print("\n[dim]Options:[/dim]")
284
312
  console.print("[dim] • Enter a number to use existing memory[/dim]")
285
313
  console.print("[dim] • Press Enter to create new memory[/dim]")
286
- console.print("[dim] • Type 's' to skip memory setup[/dim]")
287
314
 
288
315
  response = _prompt_with_default("Your choice", "").strip().lower()
289
316
 
@@ -293,9 +320,11 @@ class ConfigurationManager:
293
320
  selected = existing_memories[idx]
294
321
  _print_success(f"Using existing memory: {selected.get('name', selected.get('id'))}")
295
322
  return ("USE_EXISTING", selected.get("id"))
296
- elif response == "s":
297
- _print_success("Skipping memory configuration")
298
- return ("SKIP", None)
323
+ else:
324
+ # No existing memories found
325
+ console.print("[yellow]No existing memory resources found in your account[/yellow]")
326
+ console.print("[dim]Proceeding with new memory creation...[/dim]\n")
327
+
299
328
  except Exception as e:
300
329
  console.print(f"[dim]Could not list existing memories: {e}[/dim]")
301
330
 
@@ -303,9 +332,8 @@ class ConfigurationManager:
303
332
  return self._prompt_new_memory_config()
304
333
 
305
334
  def _prompt_new_memory_config(self) -> Tuple[str, str]:
306
- """Prompt for new memory configuration."""
307
- console.print("\n🧠 [cyan]Memory Configuration[/cyan]")
308
- console.print("[green]✓ Short-term memory is enabled by default[/green]")
335
+ """Prompt for new memory configuration (no skip option)."""
336
+ console.print("[green] Short-term memory will be enabled (default)[/green]")
309
337
  console.print(" • Stores conversations within sessions")
310
338
  console.print(" • Provides immediate context recall")
311
339
  console.print()
@@ -313,10 +341,10 @@ class ConfigurationManager:
313
341
  console.print(" • Extracts user preferences across sessions")
314
342
  console.print(" • Remembers facts and patterns")
315
343
  console.print(" • Creates session summaries")
316
- console.print(" • [dim]Note: Takes 60-90 seconds to process[/dim]")
344
+ console.print(" • [dim]Note: Takes 120-180 seconds to process[/dim]")
317
345
  console.print()
318
346
 
319
- response = _prompt_with_default("Enable long-term memory extraction? (yes/no)", "no").strip().lower()
347
+ response = _prompt_with_default("Enable long-term memory? (yes/no)", "no").strip().lower()
320
348
 
321
349
  if response in ["yes", "y"]:
322
350
  _print_success("Configuring short-term + long-term memory")
@@ -44,9 +44,11 @@ class Runtime:
44
44
  auto_create_ecr: bool = True,
45
45
  auto_create_execution_role: bool = False,
46
46
  authorizer_configuration: Optional[Dict[str, Any]] = None,
47
+ request_header_configuration: Optional[Dict[str, Any]] = None,
47
48
  region: Optional[str] = None,
48
49
  protocol: Optional[Literal["HTTP", "MCP", "A2A"]] = None,
49
50
  disable_otel: bool = False,
51
+ memory_mode: Literal["NO_MEMORY", "STM_ONLY", "STM_AND_LTM"] = "STM_ONLY",
50
52
  non_interactive: bool = True,
51
53
  ) -> ConfigureResult:
52
54
  """Configure Bedrock AgentCore from notebook using an entrypoint file.
@@ -64,13 +66,31 @@ class Runtime:
64
66
  auto_create_ecr: Whether to auto-create ECR repository
65
67
  auto_create_execution_role: Whether to auto-create execution role (makes execution_role optional)
66
68
  authorizer_configuration: JWT authorizer configuration dictionary
69
+ request_header_configuration: Request header configuration dictionary
67
70
  region: AWS region for deployment
68
71
  protocol: agent server protocol, must be either HTTP or MCP or A2A
69
72
  disable_otel: Whether to disable OpenTelemetry observability (default: False)
73
+ memory_mode: Memory configuration mode (default: "STM_ONLY")
74
+ - "NO_MEMORY": Disable memory entirely (stateless agent)
75
+ - "STM_ONLY": Short-term memory only (default)
76
+ - "STM_AND_LTM": Short-term + long-term memory with strategy extraction
70
77
  non_interactive: Skip interactive prompts and use defaults (default: True)
71
78
 
72
79
  Returns:
73
80
  ConfigureResult with configuration details
81
+
82
+ Example:
83
+ # Default: STM only (backward compatible)
84
+ runtime.configure(entrypoint='handler.py')
85
+
86
+ # Explicitly enable LTM
87
+ runtime.configure(entrypoint='handler.py', memory_mode='STM_AND_LTM')
88
+
89
+ # Disable memory entirely
90
+ runtime.configure(entrypoint='handler.py', memory_mode='NO_MEMORY')
91
+
92
+ # Invalid - raises error
93
+ runtime.configure(entrypoint='handler.py', disable_memory=True, memory_mode='STM_AND_LTM')
74
94
  """
75
95
  if protocol and protocol.upper() not in ["HTTP", "MCP", "A2A"]:
76
96
  raise ValueError("protocol must be either HTTP or MCP or A2A")
@@ -99,7 +119,7 @@ class Runtime:
99
119
  handler_dir = Path(file_path).parent
100
120
  req_file_path = handler_dir / "requirements.txt"
101
121
 
102
- all_requirements = [] # "bedrock_agentcore" # Always include bedrock_agentcore
122
+ all_requirements = []
103
123
  all_requirements.extend(requirements)
104
124
 
105
125
  req_file_path.write_text("\n".join(all_requirements))
@@ -107,6 +127,13 @@ class Runtime:
107
127
 
108
128
  final_requirements_file = str(req_file_path)
109
129
 
130
+ if memory_mode == "NO_MEMORY":
131
+ log.info("Memory disabled - agent will be stateless")
132
+ elif memory_mode == "STM_AND_LTM":
133
+ log.info("Memory configured with STM + LTM")
134
+ else: # STM_ONLY
135
+ log.info("Memory configured with STM only")
136
+
110
137
  # Configure using the operations module
111
138
  result = configure_bedrock_agentcore(
112
139
  agent_name=agent_name,
@@ -118,8 +145,10 @@ class Runtime:
118
145
  container_runtime=container_runtime,
119
146
  auto_create_ecr=auto_create_ecr,
120
147
  enable_observability=not disable_otel,
148
+ memory_mode=memory_mode,
121
149
  requirements_file=final_requirements_file,
122
150
  authorizer_configuration=authorizer_configuration,
151
+ request_header_configuration=request_header_configuration,
123
152
  region=region,
124
153
  protocol=protocol.upper() if protocol else None,
125
154
  non_interactive=non_interactive,
@@ -451,7 +451,7 @@ class MemoryManager:
451
451
  logger.info("🔎 Retrieving memory resource with ID: %s...", memory_id)
452
452
  try:
453
453
  response = self._control_plane_client.get_memory(memoryId=memory_id).get("memory", {})
454
- logger.info(" Found memory: %s", memory_id)
454
+ logger.info(" Found memory: %s", memory_id)
455
455
  return Memory(response)
456
456
  except ClientError as e:
457
457
  logger.error(" ❌ Error retrieving memory: %s", e)
@@ -1,6 +1,13 @@
1
1
  """Bedrock AgentCore operations - shared business logic for CLI and notebook interfaces."""
2
2
 
3
- from .configure import configure_bedrock_agentcore, validate_agent_name
3
+ from .configure import (
4
+ configure_bedrock_agentcore,
5
+ detect_entrypoint,
6
+ detect_requirements,
7
+ get_relative_path,
8
+ infer_agent_name,
9
+ validate_agent_name,
10
+ )
4
11
  from .destroy import destroy_bedrock_agentcore
5
12
  from .invoke import invoke_bedrock_agentcore
6
13
  from .launch import launch_bedrock_agentcore
@@ -18,6 +25,10 @@ __all__ = [
18
25
  "configure_bedrock_agentcore",
19
26
  "destroy_bedrock_agentcore",
20
27
  "validate_agent_name",
28
+ "detect_entrypoint",
29
+ "detect_requirements",
30
+ "get_relative_path",
31
+ "infer_agent_name",
21
32
  "launch_bedrock_agentcore",
22
33
  "invoke_bedrock_agentcore",
23
34
  "get_status",