
Build an Agentic Cloud Project Generator
Build an AI agent with the Strands SDK that generates tailored AWS side project ideas, then deploy it to AWS as a live web app anyone can use in a browser.
Many of us can rattle off AWS services, sketch architecture diagrams, and maybe even hold a certification, so it's strange how hard it still is to turn all that theory into something real the moment we open an empty terminal.
I wrote about one fix for this before, where I prompted an LLM to suggests tailored AWS projects and walk you through them but this time I want to try something different. We're going to build an AI agent with the Strands Agents SDK , give it custom tools, put a web page in front of it and deploy the whole thing to AWS so that anyone can use it right from a browser.
By the end you'll have a working agent that you understand line by line, along with a live site you can hand to a friend who is stuck in the same theory trap you were.
The nice part is that the agent becomes a machine for generating your next project, so you can keep coming back to it for the next one.
Find it before you build it
You can find the full code at this GitHub repo . Clone the repo by running the command below, follow the README and deploy it on AWS for yourself. The rest of this post walks through how it all works so you understand every piece you are deploying.
1
git clone https://github.com/rextheking/personalized-ai-project-generator.git
What you'll learn
The main point of this blog is learning how to build with an agentic framework, how tools, a system prompt and a model come together into something that reasons and acts. So the generator is very simple on purpose doing only one thing instead of being rich with features.
Keeping the product simple means the agent concepts stay the focus, and you finish with a clear mental model of how Strands works plus a real deployed app to show for it.
What you'll build
The build is very simple, a person tells the agent their skill level and what they want to practice. The agent returns a project idea matched to them, and a build plan for the one it recommends most, all in a single response.
Here is how the pieces fit together.

The frontend is static files on S3, served through CloudFront, the backend is a Strands agent running in Lambda behind an HTTP API and the reasoning happens in Amazon Bedrock.
Two custom tools keep the agent grounded so it suggests real services with sane time estimates instead of making things up.
Why Strands
Strands Agents SDK is an open source toolkit for building agents. You give a model a prompt and a set of tools, and it plans, calls those tools, reads the results and decides what to do next until the task is done. We will write plain Python functions and the SDK will turn them into tools the model can call.
That model-driven loop frees us from having to write if-else logic to decide when to suggest a project. You describe the tools, write a good system prompt and let the agent reason about which tool fits the request.
Before you start
You need a few things ready.
- An AWS account with permissions for Lambda, API Gateway, S3, CloudFront and IAM
- Amazon Bedrock model access turned on for Claude in your region
- Python 3.12
- Node.js and the AWS CDK CLI
- The AWS CLI, configured with your credentials
The Lambda dependencies are bundled locally with pip, so you do not need Docker or the SAM CLI to deploy.
One detail that trips people up. The newer Claude models are invoked through a cross-region inference profile, not the bare model id. That is why the default in this project is
us.anthropic.claude-sonnet-4-20250514-v1:0 with the us. prefix rather than anthropic.claude-sonnet-4-20250514-v1:0. If you get a validation error about the model id on demand throughput, the missing prefix is usually why.Step 1, set up the project folder
Create a clean working folder and a structure to hold the agent, the frontend and the infrastructure.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
personalized-ai-project-generator/
├── backend/
│ ├── agent/
│ │ ├── __init__.py
│ │ ├── generator.py
│ │ ├── tools.py
│ │ └── prompts.py
│ ├── app.py
│ ├── local_run.py
│ └── requirements.txt
├── frontend/
│ ├── index.html
│ ├── styles.css
│ └── app.js
└── infrastructure/
├── app.py # CDK app entry point
├── cdk.json
├── requirements.txt
├── Makefile
└── stacks/
└── generator_stack.pyKeeping the backend, frontend and infrastructure apart for separation of environments so you always know where to look.
Step 2, install Strands and set up a virtual environment
Work inside the backend folder and create a virtual environment so the SDK and its dependencies stay isolated from the rest of your system.
1
2
3
4
cd backend
python3.12 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install strands-agentsThe
strands-agents package is the core SDK and the only runtime dependency the agent needs, since we write our own tools. Pin it in a requirements.txt so Lambda installs the same version you developed against.1
=1.0.0Step 3, write the system prompt
The system prompt is where you give the agent its job, its boundaries and its manners. This goes in
agent/prompts.py.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SYSTEM_PROMPT = """
You are an AWS project advisor. You help people turn AWS theory into hands-on
projects they can add to a portfolio.
Someone gives you their skill level, the AWS certifications or background they
have, the services they want to practice and how much time they have. You turn
that into project ideas that fit them, then a step by step build plan for the
one they pick.
How you work:
1. When asked for project ideas, call the generate_project_ideas tool.
2. When asked for a build plan, call the build_implementation_plan tool.
3. Favor Well-Architected guidance, least-privilege IAM and free-tier choices.
4. Call out security considerations and common mistakes for each step.
5. Never invent AWS services that do not exist.
"""Notice it tells the agent which tool maps to which kind of request. The model still decides on its own, but a clear prompt makes that decision reliable.
Step 4, build the custom tools
This is the heart of the agent. A tool in Strands is a Python function with the
@tool decorator. The SDK reads the function's docstring and type hints to build the tool spec the model sees, so the docstring is part of the code, not just some comment you can skip.The first tool suggests projects. It keeps a small catalog of real service combinations in code, then filters that catalog by the person's level and interests. The model writes the friendly explanation and the tool keeps the facts straight.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from strands import tool
def generate_project_ideas(
skill_level: str,
interests: str = "",
max_results: int = 3,
) -> dict:
"""Suggest AWS project ideas tailored to a person's skill level and interests.
Args:
skill_level: One of "beginner", "intermediate" or "advanced".
interests: Comma separated topics or services, for example
"serverless, api, dynamodb".
max_results: Maximum number of project ideas to return.
"""
# filter a curated catalog by level and interests, return the matches
...The second tool turns a chosen project into an ordered build plan, each step carrying a goal and a security note.
1
2
3
4
5
6
7
8
9
def build_implementation_plan(project_title: str) -> dict:
"""Return a step by step build plan for a known project.
Args:
project_title: The title of a project from a previous suggestion.
"""
# match the title to the catalog, return ordered steps with security notes
...Why anchor everything to a catalog instead of letting the model freestyle? An agent left to invent project metadata will happily promise you a two hour build that takes two days, or reference a service that does not exist. Grounding the facts in code and letting the model handle the language gives you the best of both.
The full versions of these functions are in
backend/agent/tools.py in the repository.Step 5, assemble the agent
Now you wire the model, the prompt and the tools together. This is
generator.py and it is shorter than you might expect.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import os
from strands import Agent
from strands.models import BedrockModel
from .prompts import SYSTEM_PROMPT
from .tools import build_implementation_plan, generate_project_ideas
def build_agent(callback_handler=None) -> Agent:
model = BedrockModel(
model_id=os.environ.get(
"BEDROCK_MODEL_ID", "us.anthropic.claude-sonnet-4-20250514-v1:0"
),
region_name=os.environ.get("AWS_REGION", "us-east-1"),
temperature=0.4,
)
return Agent(
model=model,
system_prompt=SYSTEM_PROMPT,
tools=[generate_project_ideas, build_implementation_plan],
callback_handler=callback_handler,
)Reading the model id from an environment variable means you can swap models at deploy time without editing code. The
callback_handler argument controls output. Leave it as the default when running locally so you watch the agent think. Pass None inside Lambda where nobody is watching a console.Step 6, run it on your machine
Deploying before you have seen the agent work is a recipe for debugging in the worst possible place. Run it locally first. A small loop in
local_run.py reads your input and passes it to the agent.1
2
3
4
5
6
7
8
from agent.generator import build_agent
agent = build_agent()
while True:
user_input = input("you> ").strip()
if user_input.lower() in {"quit", "exit"}:
break
agent(user_input)Run it.
1
python local_run.pyThen talk to it.
1
2
I am an AWS Certified Cloud Practitioner. Suggest 3 beginner serverless
projects I can finish in an afternoon.Watch the output. You will see the agent reason, call
generate_project_ideas, read the result and write its suggestions. .1
Give me a build plan for the serverless REST API project.That in-memory context is a local convenience. The deployed Lambda is stateless, so the web app sends one prompt that asks for ideas and a build plan together, which you will wire up in the frontend step. When the terminal run works, the hard part is behind you. Everything after this is packaging.
Step 7, wrap the agent in a Lambda handler
The web app talks to the agent over HTTP, so you need a Lambda handler that takes a JSON request, runs the agent and returns JSON. This is
app.py.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import json
from agent.generator import ask
def handler(event, context):
method = event.get("requestContext", {}).get("http", {}).get("method")
if method == "OPTIONS":
return _response(200, {"ok": True})
body = json.loads(event.get("body") or "{}")
prompt = (body.get("prompt") or "").strip()
if not prompt:
return _response(400, {"error": "Field 'prompt' is required."})
answer = ask(prompt)
return _response(200, {"response": answer})Two details to note here:
The
OPTIONS check handles the CORS preflight request browsers send before a POST and the handler validates the prompt before doing anything, because every agent call costs Bedrock tokens and you do not want empty or oversized requests reaching the model. The full handler also caps prompt length and catches errors so a failure returns a clean message instead of a stack trace.
Step 8, build the frontend
The frontend is three files. An HTML form for the request, a stylesheet and a small script that posts to the API and shows the response.
The script reads the API endpoint from a
config.js file that the deploy step writes for you, so you never hardcode a URL. The agent replies in markdown, so the script renders it to HTML rather than dumping raw text with stray ## and ** symbols on the page.1
2
3
4
5
6
7
8
9
10
11
const API_ENDPOINT = window.API_ENDPOINT || "REPLACE_WITH_YOUR_API_ENDPOINT";
async function callAgent(prompt) {
const res = await fetch(API_ENDPOINT, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ prompt }),
});
const data = await res.json();
document.getElementById("response").innerHTML = renderMarkdown(data.response);
}The
renderMarkdown helper escapes the text first, so model output can never inject markup, then converts headings, bold, inline code and lists into safe HTML. The form collects skill level, background, interests and available time, then stitches them into one prompt. That prompt asks for tailored project ideas and a build plan for the top recommendation, so a single click returns both. Full markup and styles are in the frontend folder.Step 9, define the infrastructure
You could click all this together in the console, but defining it as code means you can tear it down and rebuild it the same way every time. This project uses AWS CDK, which lets you describe the stack in Python.
The stack in
infrastructure/stacks/generator_stack.py declares four things that matter.- A Lambda function running the agent, with an IAM policy scoped to Bedrock model
invocation - An HTTP API that routes
POST /generateto the function - An S3 bucket with all public access blocked
- A CloudFront distribution that reads the bucket through an Origin Access Control
As far as security posture, the bucket is never public. CloudFront is the only thing allowed to read it, and the Lambda role can invoke Bedrock models and nothing else for least privilege in practice.
The Lambda needs its Python dependencies built as Linux wheels, but you are most likely developing on a Mac or Windows machine. The stack bundles the dependencies locally with pip, targeting Linux x86_64 wheels, so you do not need Docker or the SAM CLI to produce a working Lambda package. The relevant pip flags are
--platform manylinux2014_x86_64 and --only-binary=:all:.Step 10, deploy
A Makefile wraps the deploy into a few targets. Install the CDK dependencies, bootstrap the account once, then deploy.
1
2
3
4
5
cd infrastructure
make install # create a venv and install the CDK packages
make bootstrap # first time only, per account and region
make deploy # provision Lambda, API Gateway, S3 and CloudFront
make upload-web # write config.js, upload the frontend, invalidate the cachemake deploy prints the stack outputs, including your website URL and the API endpoint. make upload-web reads those outputs, points the frontend at the live API and pushes it to S3. Open the website URL in a browser and fill in the form. One click returns project ideas plus a build plan for the top recommendation and you now have a personalized AWS project advisor running on AWS that anyone can use.
A live CloudFront distribution takes a few minutes to propagate the first time, so if the URL is not ready immediately, wait a moment and refresh.
A note on cost and safety
This site has no authentication. That is fine for a quick demo you share with a handful of people, but every request spends Bedrock tokens. Before you post the link anywhere public, add an API key with a usage plan or put Cognito in front of it, and tighten the CORS origin from
* down to your CloudFront domain. Set a budget alert too because an open AI endpoint on the internet is an open invoice.Cleaning up
When you are done, remove the resources so nothing keeps costing money.
1
2
cd infrastructure
make destroyThe S3 bucket is set to auto-delete its objects, so the teardown is clean. CloudFront takes a little while to fully delete in the background, which is normal.
Keep building with it
The point of this agent is that it does not stop being useful once it is built. As you grow, push it further.
- Add a tool that scores a finished project against the Well-Architected pillars
- Stream the response to the browser instead of waiting for the full reply
- Add conversation memory with DynamoDB so the agent recalls earlier turns
- Have it generate a CDK stack for whichever project the user picks
Every one of those is a project in itself and now you have the advisor to plan them with.
Resources
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article