Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
643 changes: 643 additions & 0 deletions documentation/docs/examples/agentcore-quickstart-example.md

Large diffs are not rendered by default.

35 changes: 33 additions & 2 deletions src/bedrock_agentcore_starter_toolkit/cli/runtime/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ def configure(
verbose=verbose,
region=region,
protocol=protocol.upper() if protocol else None,
non_interactive=non_interactive,
)

# Prepare authorization info for summary
Expand All @@ -306,8 +307,9 @@ def configure(
f"ECR Repository: [dim]"
f"{'Auto-create' if result.auto_create_ecr else result.ecr_repository or 'N/A'}"
f"[/dim]\n"
f"Authorization: [dim]{auth_info}[/dim]\n"
f"Authorization: [dim]{auth_info}[/dim]\n\n"
f"{headers_info}\n"
f"Memory: [dim]Short-term memory (30-day retention)[/dim]\n\n"
f"📄 Config saved to: [dim]{result.config_path}[/dim]\n\n"
f"[bold]Next Steps:[/bold]\n"
f" [cyan]agentcore launch[/cyan]",
Expand All @@ -328,7 +330,7 @@ def launch(
None, "--agent", "-a", help="Agent name (use 'agentcore configure list' to see available agents)"
),
local: bool = typer.Option(
False, "--local", "-l", help="Build locally and run container locally - requires Docker/Finch/Podman"
False, "--local", "-l", help="Local build + local runtime - requires Docker/Finch/Podman"
),
local_build: bool = typer.Option(
False,
Expand Down Expand Up @@ -852,6 +854,11 @@ def status(

# Determine overall status
endpoint_status = endpoint_data.get("status", "Unknown") if endpoint_data else "Not Ready"
# memory_info = ""
# if hasattr(status_json["config"], "memory_id") and status_json["config"].get("memory_id"):
# memory_type = status_json["config"].get("memory_type", "Short-term")
# memory_id = status_json["config"].get("memory_id")
# memory_info = f"Memory: [cyan]{memory_type}[/cyan] ([dim]{memory_id}[/dim])\n"
if endpoint_status == "READY":
status_text = "Ready - Agent deployed and endpoint available"
else:
Expand All @@ -867,6 +874,30 @@ def status(
f"([cyan]{endpoint_status}[/cyan])\n"
f"Region: [cyan]{status_json['config']['region']}[/cyan] | "
f"Account: [dim]{status_json['config'].get('account', 'Not available')}[/dim]\n\n"
)

# Add memory status with proper provisioning indication
if "memory_id" in status_json.get("config", {}) and status_json["config"]["memory_id"]:
memory_type = status_json["config"].get("memory_type", "Unknown")
memory_id = status_json["config"]["memory_id"]
memory_status = status_json["config"].get("memory_status", "Unknown")

# Color-code based on status
if memory_status == "ACTIVE":
panel_content += f"Memory: [green]{memory_type}[/green] ([dim]{memory_id}[/dim])\n"
elif memory_status in ["CREATING", "UPDATING"]:
panel_content += f"Memory: [yellow]{memory_type}[/yellow] ([dim]{memory_id}[/dim])\n"
panel_content += (
" [yellow]⚠️ Memory is provisioning. "
"STM will be available once ACTIVE.[/yellow]\n"
)
else:
panel_content += f"Memory: [red]{memory_type}[/red] ([dim]{memory_id}[/dim])\n"

panel_content += "\n"

# Continue building the panel
panel_content += (
f"[bold]Deployment Info:[/bold]\n"
f"Created: [dim]{agent_data.get('createdAt', 'Not available')}[/dim]\n"
f"Last Updated: [dim]"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import os
from pathlib import Path
from typing import Dict, Optional
from typing import Dict, Optional, Tuple

from ..common import _handle_error, _print_success, _prompt_with_default, console

Expand Down Expand Up @@ -198,3 +198,129 @@ def _configure_request_header_allowlist(self, existing_headers: str = "") -> dic
_print_success(f"Request header allowlist configured with {len(headers)} headers")

return {"requestHeaderAllowlist": headers}

def prompt_memory_type(self) -> tuple[bool, bool]:
"""Prompt user for memory configuration preference.
Returns:
Tuple of (enable_memory, enable_ltm)
"""
console.print("\n🧠 [cyan]Memory Configuration[/cyan]")
console.print("Short-term memory stores conversation within sessions.")
console.print("Long-term memory extracts preferences and facts across sessions.")
console.print()

# First ask if they want memory at all
enable_memory_response = _prompt_with_default("Enable memory for your agent? (yes/no)", "yes").strip().lower()

enable_memory = enable_memory_response in ["yes", "y"]

if not enable_memory:
_print_success("Memory disabled")
return False, False

# If memory is enabled, ask about long-term memory
console.print("\n[dim]Long-term memory extracts:[/dim]")
console.print(" • User preferences (e.g., 'I prefer Python')")
console.print(" • Semantic facts (e.g., 'My birthday is in January')")
console.print(" • Session summaries")
console.print()

enable_ltm_response = _prompt_with_default("Enable long-term memory extraction? (yes/no)", "no").strip().lower()

enable_ltm = enable_ltm_response in ["yes", "y"]

if enable_ltm:
_print_success("Long-term memory will be configured")
else:
_print_success("Using short-term memory only")

return enable_memory, enable_ltm

def prompt_memory_selection(self) -> Tuple[str, str]:
"""Prompt user to select existing memory or create new.
Returns:
Tuple of (action, value) where:
- action is "USE_EXISTING", "CREATE_NEW"
- value is memory_id for USE_EXISTING, mode for CREATE_NEW
"""
if self.non_interactive:
# In non-interactive mode, default to creating new STM
return ("CREATE_NEW", "STM_ONLY")

console.print("\n🧠 [cyan]Memory Configuration[/cyan]")

# Try to list existing memories
try:
from ...operations.memory.manager import MemoryManager

# Need region - will be passed from configure.py
region = self.existing_config.aws.region if self.existing_config else None
if not region:
# Fall back to new memory creation if no region
return self._prompt_new_memory_config()

memory_manager = MemoryManager(region_name=region)
existing_memories = memory_manager.list_memories(max_results=10)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in a follow up, would be nice to figure out how to list all for pre-selection (like maybe exposed via a search or displayed in 3 columns with the full names truncated)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack


if existing_memories:
console.print("\n[cyan]Existing memory resources found:[/cyan]")
for i, mem in enumerate(existing_memories, 1):
# Display memory summary
mem_id = mem.get("id", "unknown")
mem_name = mem.get("name", "")
if "memory-" in mem_id:
display_name = mem_id.split("memory-")[0] + "memory"
else:
display_name = mem_name or mem_id[:40]

console.print(f" {i}. [bold]{display_name}[/bold]")
if mem.get("description"):
console.print(f" [dim]{mem.get('description')}[/dim]")
console.print(f" [dim]ID: {mem_id}[/dim]")

console.print("\n[dim]Options:[/dim]")
console.print("[dim] • Enter a number to use existing memory[/dim]")
console.print("[dim] • Press Enter to create new memory[/dim]")
console.print("[dim] • Type 's' to skip memory setup[/dim]")

response = _prompt_with_default("Your choice", "").strip().lower()

if response.isdigit():
idx = int(response) - 1
if 0 <= idx < len(existing_memories):
selected = existing_memories[idx]
_print_success(f"Using existing memory: {selected.get('name', selected.get('id'))}")
return ("USE_EXISTING", selected.get("id"))
elif response == "s":
_print_success("Skipping memory configuration")
return ("SKIP", None)
except Exception as e:
console.print(f"[dim]Could not list existing memories: {e}[/dim]")

# Fall back to creating new memory
return self._prompt_new_memory_config()

def _prompt_new_memory_config(self) -> Tuple[str, str]:
"""Prompt for new memory configuration."""
console.print("\n🧠 [cyan]Memory Configuration[/cyan]")
console.print("[green]✓ Short-term memory is enabled by default[/green]")
console.print(" • Stores conversations within sessions")
console.print(" • Provides immediate context recall")
console.print()
console.print("[cyan]Optional: Long-term memory[/cyan]")
console.print(" • Extracts user preferences across sessions")
console.print(" • Remembers facts and patterns")
console.print(" • Creates session summaries")
console.print(" • [dim]Note: Takes 60-90 seconds to process[/dim]")
console.print()

response = _prompt_with_default("Enable long-term memory extraction? (yes/no)", "no").strip().lower()

if response in ["yes", "y"]:
_print_success("Configuring short-term + long-term memory")
return ("CREATE_NEW", "STM_AND_LTM")
else:
_print_success("Using short-term memory only")
return ("CREATE_NEW", "STM_ONLY")
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from pathlib import Path
from typing import Any, Dict, Optional, Tuple

from ...cli.runtime.configuration_manager import ConfigurationManager
from ...services.ecr import get_account_id, get_region
from ...utils.runtime.config import merge_agent_config, save_config
from ...utils.runtime.container import ContainerRuntime
Expand All @@ -13,6 +14,7 @@
BedrockAgentCoreAgentSchema,
BedrockAgentCoreDeploymentInfo,
CodeBuildConfig,
MemoryConfig,
NetworkConfiguration,
ObservabilityConfig,
ProtocolConfiguration,
Expand All @@ -38,6 +40,7 @@ def configure_bedrock_agentcore(
verbose: bool = False,
region: Optional[str] = None,
protocol: Optional[str] = None,
non_interactive: bool = False,
) -> ConfigureResult:
"""Configure Bedrock AgentCore application with deployment settings.

Expand All @@ -57,6 +60,7 @@ def configure_bedrock_agentcore(
verbose: Whether to provide verbose output during configuration
region: AWS region for deployment
protocol: agent server protocol, must be either HTTP or MCP
non_interactive: Skip interactive prompts and use defaults

Returns:
ConfigureResult model with configuration details
Expand Down Expand Up @@ -112,6 +116,51 @@ def configure_bedrock_agentcore(
else:
log.debug("No execution role provided and auto-create disabled")

if verbose:
log.debug("Prompting for memory configuration")

config_manager = ConfigurationManager(build_dir / ".bedrock_agentcore.yaml")

# New memory selection flow
action, value = config_manager.prompt_memory_selection()

memory_config = MemoryConfig()
if action == "USE_EXISTING":
# Using existing memory - just store the ID
memory_config.memory_id = value
memory_config.mode = "STM_AND_LTM" # Assume existing has strategies
memory_config.memory_name = f"{agent_name}_memory"
log.info("Using existing memory resource: %s", value)
elif action == "CREATE_NEW":
# Create new with specified mode
memory_config.mode = value # This is the mode (STM_ONLY, STM_AND_LTM, NO_MEMORY)
memory_config.event_expiry_days = 30
memory_config.memory_name = f"{agent_name}_memory"
log.info("Will create new memory with mode: %s", value)

if memory_config.mode == "STM_AND_LTM":
log.info("Memory configuration: Short-term + Long-term memory enabled")
elif memory_config.mode == "STM_ONLY":
log.info("Memory configuration: Short-term memory only")

# Check for existing memory configuration from previous launch
config_path = build_dir / ".bedrock_agentcore.yaml"
memory_id = None
memory_name = None

if config_path.exists():
try:
from ...utils.runtime.config import load_config

existing_config = load_config(config_path)
existing_agent = existing_config.get_agent_config(agent_name)
if existing_agent and existing_agent.memory and existing_agent.memory.memory_id:
memory_id = existing_agent.memory.memory_id
memory_name = existing_agent.memory.memory_name
log.info("Found existing memory ID from previous launch: %s", memory_id)
except Exception as e:
log.debug("Unable to read existing memory configuration: %s", e)

# Handle CodeBuild execution role - use separate role if provided, otherwise use execution_role
codebuild_execution_role_arn = None
if code_build_execution_role:
Expand Down Expand Up @@ -144,6 +193,8 @@ def configure_bedrock_agentcore(
log.debug(" Region: %s", region)
log.debug(" Enable observability: %s", enable_observability)
log.debug(" Requirements file: %s", requirements_file)
if memory_id:
log.debug(" Memory ID: %s", memory_id)

dockerfile_path = runtime.generate_dockerfile(
entrypoint_path,
Expand All @@ -152,6 +203,8 @@ def configure_bedrock_agentcore(
region,
enable_observability,
requirements_file,
memory_id,
memory_name,
)

# Check if .dockerignore was created
Expand Down Expand Up @@ -221,6 +274,7 @@ def configure_bedrock_agentcore(
),
authorizer_configuration=authorizer_configuration,
request_header_configuration=request_header_configuration,
memory=memory_config,
)

# Use simplified config merging
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import boto3
from botocore.exceptions import ClientError

from ...operations.memory.manager import MemoryManager
from ...services.runtime import BedrockAgentCoreClient
from ...utils.runtime.config import load_config, save_config
from ...utils.runtime.schema import BedrockAgentCoreAgentSchema, BedrockAgentCoreConfigSchema
Expand Down Expand Up @@ -77,13 +78,16 @@ def destroy_bedrock_agentcore(
# 4. Remove CodeBuild project
_destroy_codebuild_project(session, agent_config, result, dry_run)

# 5. Remove CodeBuild IAM Role
# 5. Remove memory resource
_destroy_memory(session, agent_config, result, dry_run)

# 6. Remove CodeBuild IAM Role
_destroy_codebuild_iam_role(session, agent_config, result, dry_run)

# 6. Remove IAM execution role (if not used by other agents)
# 7. Remove IAM execution role (if not used by other agents)
_destroy_iam_role(session, project_config, agent_config, result, dry_run)

# 7. Clean up configuration
# 8. Clean up configuration
if not dry_run and not result.errors:
_cleanup_agent_config(config_path, project_config, agent_config.name, result)

Expand Down Expand Up @@ -386,6 +390,42 @@ def _destroy_codebuild_project(
log.warning("Error during CodeBuild cleanup: %s", e)


def _destroy_memory(
session: boto3.Session,
agent_config: BedrockAgentCoreAgentSchema,
result: DestroyResult,
dry_run: bool,
) -> None:
"""Remove memory resource for this agent."""
if not agent_config.memory or not agent_config.memory.memory_id:
result.warnings.append("No memory configured, skipping memory cleanup")
return

try:
memory_manager = MemoryManager(region_name=agent_config.aws.region)
memory_id = agent_config.memory.memory_id

if dry_run:
result.resources_removed.append(f"Memory: {memory_id} (DRY RUN)")
return

try:
# Use the manager's delete method which handles the deletion properly
memory_manager.delete_memory(memory_id=memory_id)
result.resources_removed.append(f"Memory: {memory_id}")
log.info("Deleted memory: %s", memory_id)
except ClientError as e:
if e.response["Error"]["Code"] not in ["ResourceNotFoundException", "NotFound"]:
result.warnings.append(f"Failed to delete memory {memory_id}: {e}")
log.warning("Failed to delete memory: %s", e)
else:
result.warnings.append(f"Memory {memory_id} not found (may have been deleted already)")

except Exception as e:
result.warnings.append(f"Error during memory cleanup: {e}")
log.warning("Error during memory cleanup: %s", e)


def _destroy_codebuild_iam_role(
session: boto3.Session,
agent_config: BedrockAgentCoreAgentSchema,
Expand Down
Loading
Loading