AWS Serverless Deployment
Deploy agents to AWS Lambda for auto-scaling, serverless execution.
Architecture
Normal Mode: using request handler for chat processing
Queue Based Execution: using queues to improve the scalability (recommended for prod)
REST SYNC and REST ASYNC modes
ASYNC (WebSocket) mode
The request handler receives the incoming API request, the agent runner executes the agent logic, and the response handler persists outbound messages to the configured response store. In queue-backed modes, the three-lambda split keeps request ingestion, execution, and response persistence independent.
In WebSocket (async) mode, a WebSocket API Gateway enables real-time bidirectional communication. The connection handler manages connection lifecycle ($connect/$disconnect routes) and stores connection metadata in DynamoDB. The request handler processes incoming WebSocket messages and routes them through the queue-based pipeline, with responses broadcast back through the WebSocket connection.
Prerequisites
- AWS CLI configured
- AWS credentials with Lambda/API Gateway permissions
- Agent Kernel with AWS extras:
pip install agentkernel[aws] - For authentication:
pip install agentkernel[api,aws]
Deployment
1. Install Dependencies
The dependencies need to be installed in both the main Lambda package and the authorizer package:
Main Lambda Package:
pip install agentkernel[aws,openai]
Authorizer Lambda Package:
pip install agentkernel[api,aws]
Example Deployment Scripts:
For the main Lambda function (deploy/deploy.sh):
# Install main Lambda dependencies
uv pip install -r requirements.txt --target=dist/data
uv pip install --force-reinstall --target=dist/data agentkernel[openai,redis]
For the authorizer Lambda function (auth_deployment/create_auth_package.sh):
# Install authorizer dependencies
uv pip install --force-reinstall --no-deps agentkernel[api,aws] --target=auth_dist
2. Configure
Refer to Terraform modules for configuration details.
3. Deploy
terraform init && terraform apply
Lambda Handler
Your agent code remains the same, just import the Lambda handler:
import json
from agents import Agent as OpenAIAgent
from agentkernel.openai import OpenAIModule
from agentkernel.aws import Lambda
agent = OpenAIAgent(name="assistant", ...)
OpenAIModule([agent])
@Lambda.register("/app", method="GET")
def custom_app_handler(event, context):
return {"receivedEventPayload": dict(event), "response": "Hello! from AK 'app'"}
@Lambda.register("/app_info", method="POST")
def custom_app_info_handler(event, context):
payload = json.loads(event.get("body") or "{}")
return {"receivedEventPayload": dict(event), "request": payload, "response": "Hello! from AK 'app_info'"}
handler = Lambda.handler
Lambda Environment Variables
The Lambda router automatically reads the following environment variables to correctly map incoming API paths:
- API_BASE_PATH – Base path mapping without leading slash. Example:
apiorprod - API_VERSION – Version segment. Example:
v1 - AGENT_ENDPOINT – The default chat endpoint segment. Example:
chat
These environment variables are automatically configured by the Terraform module based on the api_base_path, api_version, and agent_endpoint variables in your Terraform configuration.
NOTE: If you wrap our Lambda with your own API Gateway and deployment method, you are responsible for setting these environment variables. If they are not provided, only the default chat handler may work and custom routes may not resolve as expected.
NOTE: If you want to override base paths you have to define them in the
main.tffile. Also note that the chat endpoint path which is defined in themain.tffile will be using our default chat lambda handler function, therefore it is not possible to define a custom lambda function for the default chat endpoint path
API Gateway Authentication
Authentication is now mandatory for WebSocket mode and recommended for REST modes. The serverless module supports custom token validation with Lambda authorizers.
When Authentication is Enabled
Authentication infrastructure will only be created if you define an authorizer object with all required fields mentioned below in your main.tf file:
Required Fields:
function_name- Name for the authorizer Lambda functionhandler_path- Path to the authorizer Lambda handler (e.g.,auth.handler)package_type- Deployment type (Image,LocalZip, orS3Zip)package_path- Path to authorizer deployment packagemodule_name- Authorizer module name
Optional Fields:
description- Description of the authorizer function (defaults to "API Gateway Lambda Authorizer")result_ttl_in_seconds- Cache TTL for authorization results (default: 150)timeout- Authorizer Lambda timeout in seconds (default: 30)memory_size- Authorizer Lambda memory size in MB (default: 128)layers- List of Lambda layer ARNs to attach (default: [])environment_variables- Environment variables for authorizer
Important Notes:
- For WebSocket mode (
execution_mode = "async"), authentication is mandatory and is handled by the WebSocket connection handler Lambda - The bearer token must include a
userIdclaim for WebSocket connections - If the
authorizerobject is not provided or any required field is missing, no authorizer infrastructure will be created and your REST endpoints will be publicly accessible - For WebSocket mode, the connection handler Lambda implements its own authentication logic
Auth Lambda Handler
You need to create a separate auth lambda logic by extending the AuthValidator class:
from typing import Optional
from agentkernel.api import AuthValidator, ValidationContext, ValidationResult
from agentkernel.aws import APIGatewayAuthorizer
import jwt
class CustomAuthTokenValidator(AuthValidator):
def validate(self, token: str, context: Optional[ValidationContext] = None) -> ValidationResult:
"""Validate JWT token and return validation result."""
payload = jwt.decode(token, options={"verify_signature": False})
print("Payload", payload)
email = payload.get("email", "")
if email == "test@test.com":
return ValidationResult(is_valid=True)
return ValidationResult(is_valid=False)
# APIGatewayAuthorizer defines the auth lambda handler
handler = APIGatewayAuthorizer(validator=CustomAuthTokenValidator()).handle
WebSocket Connection Handler Authentication
For WebSocket mode, authentication is handled by the WebSocket connection handler Lambda. The connection handler validates the bearer token during the $connect route:
import jwt
from agentkernel.aws import WebsocketConnectionHandler
from agentkernel.auth import AuthValidator, ValidationResult
class CustomAuthTokenValidator(AuthValidator):
def validate(self, token: str) -> ValidationResult:
"""Validate JWT token and return validation result."""
try:
payload = jwt.decode(token, options={"verify_signature": False})
email = payload.get("email", "")
user_id = payload.get("userId", "")
if user_id == "user-1" and email == "test@test.com":
return ValidationResult(is_valid=True, claims={"userId": user_id})
return ValidationResult(is_valid=False, error_msg="Invalid user ID or email in token")
except Exception as e:
return ValidationResult(is_valid=False, error_msg=f"Token validation failed: {str(e)}")
handler = WebsocketConnectionHandler.set_auth_validator(CustomAuthTokenValidator()).handler
Important: The bearer token must include a userId claim for WebSocket connections. This userId is used to map WebSocket connections to users in the DynamoDB connection table. The userId should be returned in the claims dictionary of the ValidationResult.
Terraform Configuration
To enable authentication, configure the authorizer in your main.tf by defining the authorizer object:
module "serverless_agents" {
# ... other configuration
# Defining API Gateway Authorizer (optional - only creates if all required variables are defined)
authorizer = {
description = "API Gateway Lambda Authorizer"
function_name = "gtwy-auth"
handler_path = "lambda.handler"
package_path = "../auth_deployment/auth_dist.zip"
package_type = "S3Zip" # or "LocalZip" or "Image"
module_name = "auth"
# Optional authorizer settings
# result_ttl_in_seconds = 0
# environment_variables = {
# "SOME_OTHER_KEY" = "Some Other Value"
# }
}
}
Required Authorizer Fields (for auth infrastructure creation):
function_name- Name for the authorizer Lambda functionhandler_path- Path to the authorizer handler (e.g.,lambda.handler)package_type- Package type (LocalZip,S3Zip, orImage)package_path- Path to authorizer package (required for all package types)module_name- Authorizer module name (required for all package types, especially S3Zip)
Optional Authorizer Fields:
description- Description for authorizer Lambda function (default: "API Gateway Lambda Authorizer")result_ttl_in_seconds- Cache TTL for authorizer results (default: 150)environment_variables- Environment variables for authorizer Lambda
Deployment Packages
You need two separate deployment packages:
- Main Lambda Package - Contains your agent logic and backend code
- Auth Lambda Package - Contains only the authentication logic (if enabled)
File Structure Example:
your-project/
├── lambda.py # Main agent handler
├── lambda_auth.py # Authorizer handler
├── build.sh # Build script for dependencies
├── config.yaml # Configuration file
├── requirements.txt # Generated dependencies
├── pyproject.toml # Python project configuration
├── deploy/
│ ├── deploy.sh # Deployment script
│ ├── main.tf # Terraform configuration
│ ├── variables.tf # Terraform variables
│ ├── outputs.tf # Terraform outputs
│ └── terraform.tfvars # Terraform variable values
├── dist/ # Main Lambda package directory
├── dist_auth/ # Authorizer package directory
└── dist_auth.zip # Authorizer package zip file
Creating the Deployment Packages: The deployment script automatically creates both packages:
#!/bin/bash
set -e # exit if any command in this script fails
# Create main lambda deployment package
echo "Creating main deployment package..."
create_deployment_package() {
pushd ../
rm -rf dist
mkdir -p dist/data
uv export --no-hashes > requirements.txt
if [[ ${1-} != "local" ]]; then
uv pip install -r requirements.txt --target=dist/data
else
uv pip install -r requirements.txt --target=dist/data --find-links ../../../ak-py/dist
uv pip install --force-reinstall --target=dist/data --find-links ../../../ak-py/dist agentkernel[openai,redis,auth] || true
fi
cp -r lambda.py config.yaml dist/data
popd || exit 1
cp Dockerfile ../dist/
}
# Create auth deployment package
echo "Creating auth deployment package..."
create_auth_deployment_package() {
pushd ../
rm -rf dist_auth dist_auth.zip
mkdir -p dist_auth
if [[ ${1-} != "local" ]]; then
uv pip install --force-reinstall --no-deps agentkernel[api,aws,auth] --target=dist_auth
else
uv pip install --force-reinstall --no-deps agentkernel[api,aws,auth] --target=dist_auth --find-links ../../../ak-py/dist
fi
uv pip install --group auth --target=dist_auth
cp -r lambda_auth.py dist_auth/
cd dist_auth && zip -r ../dist_auth.zip .
popd || exit 1
}
create_deployment_package $1
create_auth_deployment_package $1
# Deploy with Terraform
terraform init
terraform apply
The auth package script should run automatically when executing ./deploy.sh. You can customize the script paths and structure, but you must provide two separate packages to the Terraform configuration via the package_path (for main Lambda) and authorizer.package_path (for auth Lambda) variables.
Queue Mode
Queue mode is a Terraform configuration that enables SQS-driven asynchronous processing. When queue_mode = true, the infrastructure creates input and output SQS queues, an agent runner Lambda to process queued requests, and a response handler Lambda to store responses. When queue_mode = false, only the request handler Lambda is used for direct synchronous processing.
At runtime, queue mode is determined by whether queue URLs are configured in the execution configuration (execution.queues.input.url). This allows the same Lambda code to work in both modes based on configuration.
Lambda Handlers
If queue mode is disabled, only the request handler is needed. If queue mode is enabled, you need all these Lambda handlers: request handler, agent runner, and response handler. For WebSocket mode (execution_mode = "async"), you additionally need a WebSocket connection handler.