Skip to content
Open
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
20 changes: 19 additions & 1 deletion documentation/docs/user-guide/runtime/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,11 @@ agentcore launch # Uses CodeBuild - no Docker needed

### 💻 Local Development
```bash
agentcore launch --local # Build and run locally
agentcore launch --local # Build and run locally (port 8080)
AGENTCORE_RUNTIME_LOCAL_PORT=9000 agentcore launch --local # Custom port

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Lets only introduce this in troubleshooting step in quick start around conflict ports, risk we are seeing with different ports is that runtime supports only a fixed port, so didn't want to cause confusion for devs in default cases

```
- **Fast iteration** - immediate feedback and debugging
- **Flexible ports** - avoid conflicts with `AGENTCORE_RUNTIME_LOCAL_PORT`
- **Requires:** Docker, Finch, or Podman

### 🔧 Hybrid Build
Expand Down Expand Up @@ -265,4 +267,20 @@ agentcore launch
agentcore status
```

### Custom Port Configuration
```bash
# Use custom port for local development
export AGENTCORE_RUNTIME_LOCAL_PORT=9000
agentcore launch --local

# Test with custom port
curl -X POST http://localhost:9000/invocations \
-H "Content-Type: application/json" \
-d '{"prompt": "Hello"}'

# Run multiple agents simultaneously
AGENTCORE_RUNTIME_LOCAL_PORT=8081 agentcore launch --local & # Agent 1
AGENTCORE_RUNTIME_LOCAL_PORT=8082 agentcore launch --local & # Agent 2
```

The AgentCore Runtime SDK provides everything needed to build, test, and deploy production-ready AI agents with minimal setup and maximum flexibility.
41 changes: 40 additions & 1 deletion documentation/docs/user-guide/runtime/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ python my_agent.py
curl -X POST http://localhost:8080/invocations \
-H "Content-Type: application/json" \
-d '{"prompt": "Hello!"}'

# If using custom port (e.g., AGENTCORE_RUNTIME_LOCAL_PORT=9000):
# curl -X POST http://localhost:9000/invocations \
# -H "Content-Type: application/json" \
# -d '{"prompt": "Hello!"}'
```

✅ **Success**: You should see a response like `{"result": "Hello! I'm here to help..."}`
Expand Down Expand Up @@ -94,8 +99,26 @@ agentcore invoke '{"prompt": "tell me a joke"}'

**"Port 8080 already in use"**
```bash
# Find and stop the process using port 8080
# Option 1: Find and stop the process using port 8080
lsof -ti:8080 | xargs kill -9

# Option 2: Use a different port (recommended)
export AGENTCORE_RUNTIME_LOCAL_PORT=9000
agentcore launch --local

# Option 3: One-time custom port
AGENTCORE_RUNTIME_LOCAL_PORT=3000 agentcore launch --local
```

**Multiple local agents**
```bash
# Run multiple agents on different ports
AGENTCORE_RUNTIME_LOCAL_PORT=8081 agentcore launch --local # Agent 1
AGENTCORE_RUNTIME_LOCAL_PORT=8082 agentcore launch --local # Agent 2

# Test each agent
curl -X POST http://localhost:8081/invocations -H "Content-Type: application/json" -d '{"prompt": "Hello Agent 1"}'
curl -X POST http://localhost:8082/invocations -H "Content-Type: application/json" -d '{"prompt": "Hello Agent 2"}'
```

**"Permission denied" errors**
Expand Down Expand Up @@ -144,6 +167,22 @@ agentcore launch --local # Build and run locally (requires Docker/Finch/Podman)
```
Perfect for development, rapid iteration, debugging

**Custom Port Configuration**
```bash
# Run on custom port (useful when 8080 is in use)
export AGENTCORE_RUNTIME_LOCAL_PORT=9000
agentcore launch --local

# Test with custom port
curl -X POST http://localhost:9000/invocations \
-H "Content-Type: application/json" \
-d '{"prompt": "Hello!"}'

# Or configure and launch in one command
AGENTCORE_RUNTIME_LOCAL_PORT=9000 agentcore launch --local
```
The `AGENTCORE_RUNTIME_LOCAL_PORT` environment variable accepts any valid port (1-65535)

**🔧 Hybrid: Local Build + Cloud Runtime**
```bash
agentcore launch --local-build # Build locally, deploy to cloud (requires Docker/Finch/Podman)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
validate_agent_name,
)
from ...utils.runtime.entrypoint import parse_entrypoint
from ...utils.runtime.port_config import get_local_port
from ..common import _handle_error, _print_success, console
from .configuration_manager import ConfigurationManager

Expand Down Expand Up @@ -385,7 +386,8 @@ def launch(
if result.mode == "local":
_print_success(f"Docker image built: {result.tag}")
_print_success("Ready to run locally")
console.print("Starting server at http://localhost:8080")
local_port = get_local_port()
console.print(f"Starting server at http://localhost:{local_port}")
console.print("[yellow]Press Ctrl+C to stop[/yellow]\n")

if result.runtime is None or result.port is None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from ...services.runtime import BedrockAgentCoreClient, generate_session_id
from ...utils.runtime.config import load_config, save_config
from ...utils.runtime.port_config import get_local_port
from ...utils.runtime.schema import BedrockAgentCoreConfigSchema
from .models import InvokeResult

Expand Down Expand Up @@ -66,8 +67,9 @@ def invoke_bedrock_agentcore(
workload_name=workload_name, user_token=bearer_token, user_id=user_id
)["workloadAccessToken"]

# TODO: store and read port config of local running container
client = LocalBedrockAgentCoreClient("http://0.0.0.0:8080")
# Use configurable port for local container connection
local_port = get_local_port()
client = LocalBedrockAgentCoreClient(f"http://0.0.0.0:{local_port}")
response = client.invoke_endpoint(session_id, payload_str, workload_access_token)

else:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from ...services.runtime import BedrockAgentCoreClient
from ...utils.runtime.config import load_config, save_config
from ...utils.runtime.container import ContainerRuntime
from ...utils.runtime.port_config import get_local_port
from ...utils.runtime.schema import BedrockAgentCoreAgentSchema, BedrockAgentCoreConfigSchema
from .create_role import get_or_create_runtime_execution_role
from .models import LaunchResult
Expand Down Expand Up @@ -342,7 +343,7 @@ def launch_bedrock_agentcore(
return LaunchResult(
mode="local",
tag=tag,
port=8080,
port=get_local_port(),
runtime=runtime,
env_vars=env_vars,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from ...cli.common import _handle_warn
from .entrypoint import detect_dependencies, get_python_version
from .port_config import get_local_port

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -247,12 +248,12 @@ def build(self, dockerfile_dir: Path, tag: str, platform: Optional[str] = None)

return self._execute_command(cmd)

def run_local(self, tag: str, port: int = 8080, env_vars: Optional[dict] = None) -> subprocess.CompletedProcess:
def run_local(self, tag: str, port: Optional[int] = None, env_vars: Optional[dict] = None) -> subprocess.CompletedProcess:
"""Run container locally.

Args:
tag: Docker image tag to run
port: Port to expose (default: 8080)
port: Port to expose (default: from AGENTCORE_RUNTIME_LOCAL_PORT env var or 8080)
env_vars: Additional environment variables to pass to container
"""
if not self.has_local_runtime:
Expand All @@ -263,6 +264,10 @@ def run_local(self, tag: str, port: int = 8080, env_vars: Optional[dict] = None)
"💡 For local runs, please install Docker, Finch, or Podman"
)

# Use provided port or get from environment/default
if port is None:
port = get_local_port()

container_name = f"{tag.split(':')[0]}-{int(time.time())}"
cmd = [self.runtime, "run", "-it", "--rm", "-p", f"{port}:8080", "--name", container_name]

Expand Down
19 changes: 19 additions & 0 deletions src/bedrock_agentcore_starter_toolkit/utils/runtime/port_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""Port configuration utilities for Bedrock AgentCore."""

import os


def get_local_port() -> int:
"""Get the local port from environment variable or default to 8080.

Returns:
int: The port number to use for local runtime
"""
port_str = os.getenv("AGENTCORE_RUNTIME_LOCAL_PORT", "8080")
try:
port = int(port_str)
if port < 1 or port > 65535:
raise ValueError(f"Port must be between 1 and 65535, got {port}")
return port
except ValueError as e:
raise ValueError(f"Invalid port value in AGENTCORE_RUNTIME_LOCAL_PORT: {port_str}") from e
52 changes: 50 additions & 2 deletions tests/operations/runtime/test_invoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ def test_invoke_local_mode_success(self, tmp_path):
workload_name="existing-workload-456", user_token=None, user_id=None
)

# Verify LocalBedrockAgentCoreClient was created with correct URL
# Verify LocalBedrockAgentCoreClient was created with correct URL (using default port)
mock_local_client_class.assert_called_once_with("http://0.0.0.0:8080")

# Verify local client invoke_endpoint was called correctly
Expand Down Expand Up @@ -348,7 +348,7 @@ def test_invoke_local_mode_with_bearer_token(self, tmp_path):
workload_name="test-workload-789", user_token=bearer_token, user_id=user_id
)

# Verify LocalBedrockAgentCoreClient was used
# Verify LocalBedrockAgentCoreClient was used (using default port)
mock_local_client_class.assert_called_once_with("http://0.0.0.0:8080")

# Verify local client invoke was called with workload token
Expand Down Expand Up @@ -422,6 +422,54 @@ def test_invoke_local_mode_creates_workload_if_missing(self, tmp_path):
# Verify result
assert result.response == {"response": "workload creation test"}

def test_invoke_local_mode_custom_port(self, tmp_path, monkeypatch):
"""Test invoke_bedrock_agentcore local mode uses custom port from environment variable."""
# Set custom port via environment variable
monkeypatch.setenv("AGENTCORE_RUNTIME_LOCAL_PORT", "9000")

# Create config file
config_path = tmp_path / ".bedrock_agentcore.yaml"
agent_config = BedrockAgentCoreAgentSchema(
name="test-agent",
entrypoint="test.py",
aws=AWSConfig(
region="us-west-2", network_configuration=NetworkConfiguration(), observability=ObservabilityConfig()
),
bedrock_agentcore=BedrockAgentCoreDeploymentInfo(),
oauth_configuration={"workload_name": "test-workload-custom-port"},
)
project_config = BedrockAgentCoreConfigSchema(default_agent="test-agent", agents={"test-agent": agent_config})
save_config(project_config, config_path)

payload = {"message": "Test custom port"}

with (
patch(
"bedrock_agentcore_starter_toolkit.operations.runtime.invoke.IdentityClient"
) as mock_identity_client_class,
patch(
"bedrock_agentcore_starter_toolkit.services.runtime.LocalBedrockAgentCoreClient"
) as mock_local_client_class,
):
# Mock IdentityClient
mock_identity_client = Mock()
mock_identity_client.get_workload_access_token.return_value = {"workloadAccessToken": "custom-port-token"}
mock_identity_client_class.return_value = mock_identity_client

# Mock LocalBedrockAgentCoreClient
mock_local_client = Mock()
mock_local_client.invoke_endpoint.return_value = {"response": "custom port response"}
mock_local_client_class.return_value = mock_local_client

# Call with local_mode=True
result = invoke_bedrock_agentcore(config_path, payload, local_mode=True)

# Verify LocalBedrockAgentCoreClient was created with custom port
mock_local_client_class.assert_called_once_with("http://0.0.0.0:9000")

# Verify result
assert result.response == {"response": "custom port response"}


class TestGetWorkloadName:
"""Test _get_workload_name functionality."""
Expand Down
26 changes: 25 additions & 1 deletion tests/operations/runtime/test_launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ def test_launch_local_mode(self, mock_container_runtime, tmp_path):
# Verify local mode result
assert result.mode == "local"
assert result.tag == "bedrock_agentcore-test-agent:latest"
assert result.port == 8080
assert result.port == 8080 # Default port when env var not set
assert hasattr(result, "runtime")
mock_container_runtime.build.assert_called_once()

Expand Down Expand Up @@ -798,6 +798,30 @@ def test_launch_with_codebuild_from_main_function(self, mock_boto3_clients, mock
# Check that env_vars parameter was passed to _launch_with_codebuild
assert mock_launch_with_codebuild.call_args.kwargs["env_vars"] == test_env_vars

def test_launch_local_mode_custom_port(self, mock_container_runtime, tmp_path, monkeypatch):
"""Test local deployment with custom port from environment variable."""
# Set custom port via environment variable
monkeypatch.setenv("AGENTCORE_RUNTIME_LOCAL_PORT", "9000")

config_path = create_test_config(tmp_path)

# Mock successful build
mock_container_runtime.build.return_value = (True, ["Build successful"])
mock_container_runtime.has_local_runtime = True

with patch(
"bedrock_agentcore_starter_toolkit.operations.runtime.launch.ContainerRuntime",
return_value=mock_container_runtime,
):
result = launch_bedrock_agentcore(config_path, local=True)

# Verify local mode result uses custom port
assert result.mode == "local"
assert result.tag == "bedrock_agentcore-test-agent:latest"
assert result.port == 9000 # Custom port from environment variable
assert hasattr(result, "runtime")
mock_container_runtime.build.assert_called_once()


class TestEnsureExecutionRole:
"""Test _ensure_execution_role functionality."""
Expand Down
66 changes: 66 additions & 0 deletions tests/utils/runtime/test_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,72 @@ def test_run_local_with_env_vars(self, mock_boto3_clients, mock_subprocess):
result = runtime.run_local("test:latest", 8080, env_vars)
assert result.returncode == 0

def test_run_local_default_port(self, mock_boto3_clients, monkeypatch):
"""Test run_local uses default port when none specified."""
# Ensure env var is not set
monkeypatch.delenv("AGENTCORE_RUNTIME_LOCAL_PORT", raising=False)

with patch.object(ContainerRuntime, "_is_runtime_installed", return_value=True):
runtime = ContainerRuntime("docker")

with patch("subprocess.run") as mock_run:
# Mock successful subprocess call
mock_run.return_value.returncode = 0

# Call without specifying port
result = runtime.run_local("test:latest")
assert result.returncode == 0

# Verify the docker run command used default port 8080
call_args = mock_run.call_args[0][0]
assert "-p" in call_args
port_index = call_args.index("-p") + 1
assert call_args[port_index] == "8080:8080"

def test_run_local_custom_port_from_env(self, mock_boto3_clients, monkeypatch):
"""Test run_local uses custom port from environment variable."""
# Set custom port via environment variable
monkeypatch.setenv("AGENTCORE_RUNTIME_LOCAL_PORT", "9000")

with patch.object(ContainerRuntime, "_is_runtime_installed", return_value=True):
runtime = ContainerRuntime("docker")

with patch("subprocess.run") as mock_run:
# Mock successful subprocess call
mock_run.return_value.returncode = 0

# Call without specifying port (should use env var)
result = runtime.run_local("test:latest")
assert result.returncode == 0

# Verify the docker run command used custom port 9000
call_args = mock_run.call_args[0][0]
assert "-p" in call_args
port_index = call_args.index("-p") + 1
assert call_args[port_index] == "9000:8080"

def test_run_local_explicit_port_overrides_env(self, mock_boto3_clients, monkeypatch):
"""Test run_local explicit port parameter overrides environment variable."""
# Set custom port via environment variable
monkeypatch.setenv("AGENTCORE_RUNTIME_LOCAL_PORT", "9000")

with patch.object(ContainerRuntime, "_is_runtime_installed", return_value=True):
runtime = ContainerRuntime("docker")

with patch("subprocess.run") as mock_run:
# Mock successful subprocess call
mock_run.return_value.returncode = 0

# Call with explicit port (should override env var)
result = runtime.run_local("test:latest", port=7000)
assert result.returncode == 0

# Verify the docker run command used explicit port 7000
call_args = mock_run.call_args[0][0]
assert "-p" in call_args
port_index = call_args.index("-p") + 1
assert call_args[port_index] == "7000:8080"

def test_dockerfile_generation_with_wheelhouse(self, tmp_path):
"""Test Dockerfile generation when wheelhouse directory exists."""
with patch.object(ContainerRuntime, "_is_runtime_installed", return_value=True):
Expand Down
Loading
Loading