
Build Your First AI Agent with AWS Free Tier
A step-by-step guide to building an AI agent in Python — starting with a free local model, then upgrading to AWS Bedrock with a single line change. You'll end up with a working tabletop RPG games master that rolls dice, looks up rules, and tracks your character, all in about 40 lines of code.
Everyone's talking about AI agents. Programs that can reason, make decisions, and take actions on their own. But most of the tutorials I've seen either hand-wave the interesting parts or dump you into a framework with zero explanation of what's actually happening underneath.
So let's build one. A tabletop RPG games master that rolls dice, looks up rules, and tracks your character, all driven by an AI model deciding what to do next. We'll start on your laptop with a free local model, then flip one line of code to upgrade to a cloud-hosted model on AWS.
Here's what the finished product looks like:

Agent Game Play
What Actually Makes Something an "Agent"?
The word "agent" gets thrown around a lot, so let's pin it down. An AI agent is a program that interacts with its environment, reasons about tasks, and takes actions to achieve goals. That's it. No magic, no sentience. Just a loop.
Every agent is built from three components:
A model (the brain). The large language model that does the reasoning. It reads the conversation, thinks about what to do next, and decides whether to respond directly or use a tool. The model doesn't execute code or access the outside world. It just thinks and decides.
Tools (the hands). Functions that the agent can call to interact with the world. Rolling dice, looking up information, calling an API, updating a database. You define the tools; the model decides when and how to use them.
A system prompt (the personality). The instruction manual you give the brain before the conversation starts. "You are a helpful games master." "Always respond in character." "Never reveal the answer directly." The system prompt shapes everything.
These three components come together in what's called the agentic loop:

The Agentic Loop
The model receives input, reasons about it, and either responds directly or calls a tool. If it calls a tool, the result gets fed back into the model, and it reasons again. Reason → act → observe → repeat. That cycle is what makes an agent an agent.
The important bit: the model decides which tools to use and when. You define the tools and the system prompt, but the model drives the loop. A more capable model makes better decisions, which means a more capable agent.
Why Build in the Cloud?
You can run a model entirely on your laptop. Tools like Ollama let you download and run open-weight models locally, and we'll actually start there. It's a great way to learn.
But local models have limits. They're smaller, less capable, and will struggle with complex reasoning or multi-step tool use. When you're building an agent that needs to juggle context, pick the right tool at the right moment, and produce coherent responses, model quality matters a lot.
That's where AWS comes in. Amazon Bedrock gives you access to foundation models from Amazon, Anthropic, Meta, Mistral, and others, all through a single API. If you have free tier credits, you can experiment with these models at no cost while you learn.
We'll start local, prove the concept, then upgrade to the cloud.
Setting Up
You'll need:
- Python 3.10+
- uv (a fast Python package manager)
- Ollama installed and running (for the local model step, skip this if you want to go straight to AWS)
Create a new project:
1
2
3
mkdir agent-quest && cd agent-quest
uv init
uv add 'strands-agents[ollama]'We're using the Strands Agents SDK , an open-source framework for building agents in Python. It handles the agentic loop, tool execution, and model integration so you can focus on what your agent actually does.
Pull a local model:
1
ollama pull llama3.1This downloads Meta's Llama 3.1 8B model (~4.7 GB). It supports tool calling and runs on most modern laptops. Make sure the Ollama app is running before you continue.
Your First Agent: The Games Master
Let's build a tabletop RPG games master that can roll dice and narrate a story. It's simple enough for a local model to handle, but demonstrates all three components: a system prompt, a tool, and the agentic loop.
Create
agent.py:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import random
from strands import Agent, tool
from strands.models.ollama import OllamaModel
def roll_dice(sides: int, count: int = 1) -> str:
"""Roll dice and return the results.
Args:
sides: Number of sides on each die (e.g. 6 for a standard die, 20 for a d20)
count: How many dice to roll
"""
rolls = [random.randint(1, sides) for _ in range(count)]
total = sum(rolls)
if count == 1:
return f"🎲 Rolled a d{sides}: {rolls[0]}"
return f"🎲 Rolled {count}d{sides}: {rolls} (Total: {total})"
model = OllamaModel(host="http://localhost:11434", model_id="llama3.1")
agent = Agent(
model=model,
system_prompt="""You are a dramatic and entertaining tabletop RPG games master.
You narrate scenes vividly and use dice rolls to determine outcomes.
When a player attempts an action that involves chance, roll appropriate dice
and narrate the result. A d20 roll of 15 or higher is generally a success.
Keep responses concise but atmospheric.""",
tools=[roll_dice],
)
agent("I kick open the tavern door and stride up to the bar. 'Barkeep! Your finest ale!'")Run it:
1
uv run agent.py
Agent Gameplay
The agent receives your message, the model reasons about the scene, decides it needs a dice roll to determine what happens, calls
roll_dice, gets the result, and narrates the outcome. The model chose to roll dice on its own. That's the agentic loop in action.Upgrading to AWS
The local model works, but you'll probably notice it's a bit clumsy with tool calls and the narration is... functional at best. Let's swap it for something more powerful.
What's Available on Amazon Bedrock?
Amazon Bedrock gives you access to a range of foundation models through a single API:
| Provider | Models | Strengths |
|---|---|---|
| Amazon | Nova 2 Lite, Nova Micro, Nova Lite, Nova Pro, Nova Premier | Broad range — from ultra-low-cost (Micro) to advanced reasoning (Nova 2) |
| Anthropic | Claude (Haiku, Sonnet, Opus) | Strong reasoning, excellent tool use |
| Meta | Llama 4, Llama 3.3 | Open-weight, good all-rounders |
| Mistral | Mistral Large, Ministral | Efficient, multilingual |
Your Free Tier Credits
If you're a new AWS customer, you received free tier credits when you signed up. These can be applied to Bedrock usage, so you can experiment without worrying about cost. A few things to know:
- Credits cover inference costs. Every time your agent talks to the model, that's an inference call.
- Different models cost different amounts. Amazon Nova Micro is the cheapest; larger models cost more per token.
- Set up cost monitoring. Always. You should know exactly what you're spending.
We'll use Amazon Nova 2 Lite, Amazon's current-generation model. It's fast, supports tool use, adds real step-by-step reasoning, and is a big step up from the local 8B model.
Enable Model Access
Model access on Bedrock is enabled by default for most providers. To confirm:
- Open the Amazon Bedrock console
- Click Model catalog in the sidebar
- Find Amazon Nova 2 Lite and verify it shows as available

Amazon Nova 2
No provisioning, no infrastructure setup. The models are ready to call through the API.
Switch Your Agent
My favourite part. The only thing that changes is the model:
1
2
3
4
5
6
7
# Before (Ollama, local)
from strands.models.ollama import OllamaModel
model = OllamaModel(host="http://localhost:11434", model_id="llama3.1")
# After (Bedrock, cloud)
from strands.models import BedrockModel
model=BedrockModel(model_id="us.amazon.nova-2-lite-v1:0")That's it. The SDK picks up your AWS credentials from the environment, connects to Bedrock, and your agent is now powered by a cloud-hosted model. Tools, system prompt, everything else stays the same. Only the brain changed.
Run it again and you should immediately notice the difference. The narration is richer, the tool calls are more intentional, and the model handles the scene more naturally.
Adding a Chat Loop
A cloud-hosted model handles multi-turn conversation much better than a small local one, so let's add a chat interface and actually play the game:
1
2
3
4
5
6
7
8
agent("Set the scene. I'm a wandering adventurer arriving at a small village at dusk.")
while True:
user_input = input("\nYou \> ")
if user_input.lower() in ("quit", "exit"):
print("Thanks for playing!")
break
agent(user_input)The agent sets the opening scene, then you're in control. Each message builds on the last because the SDK manages the conversation history for you.

More Agent Gameplay
This is a real, playable text adventure, and it's about 40 lines of code.
Making the Agent Smarter with More Tools
A more capable model is better at deciding. It can juggle more tools, handle complex logic, and maintain context over longer interactions. So let's give it more to work with.
We'll add two new capabilities:
- A rule book so the agent can look up game mechanics
- Player state so the agent can track HP, inventory, and abilities
The Rule Book Tool
Create a
rulebook.txt with sections for combat, skill checks, magic, and tavern activities. (Grab it from the code samples repo. ) Then give the agent a tool to search it: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def lookup_rule(topic: str) -> str:
"""Look up rules from the game's rule book.
Args:
topic: The topic to search for (e.g. 'combat', 'magic', 'skill checks')
"""
with open("rulebook.txt", "r") as f:
content = f.read()
topic_lower = topic.lower()
sections = content.split("## ")
for section in sections:
if topic_lower in section.lower():
return f"📖 Rule Book — {section.strip()}"
return f"📖 No rules found for '{topic}'."This is intentionally simple, just keyword matching on a text file. In a production system you'd probably use vector search and embeddings (RAG). But the pattern is what matters. The model decides it needs information, calls the tool, and uses the result to inform its response.
The Player State Tools
We track HP, inventory, and spell slots in memory, and give the agent tools to read and update that state:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
player = {
"name": "Adventurer",
"hp": 20, "max_hp": 20,
"strength": 3,
"spell_slots": 3,
"inventory": ["rusty sword", "leather armor", "10 gold coins"],
}
def get_player_state() -> str:
"""Get the current state of the player character including HP, inventory, and abilities."""
return (
f"🧙 {player['name']}\n"
f" HP: {player['hp']}/{player['max_hp']}\n"
f" Strength Modifier: +{player['strength']}\n"
f" Spell Slots: {player['spell_slots']}\n"
f" Inventory: {', '.join(player['inventory'])}"
)
def update_player_state(
hp_change: int = 0,
add_item: str = "",
remove_item: str = "",
use_spell_slot: bool = False,
) -> str:
"""Update the player's state after an event.
Args:
hp_change: Amount to change HP by (positive for healing, negative for damage)
add_item: An item to add to inventory
remove_item: An item to remove from inventory
use_spell_slot: Whether to consume a spell slot
"""
changes = []
if hp_change != 0:
player["hp"] = max(0, min(player["max_hp"], player["hp"] + hp_change))
direction = "healed" if hp_change \> 0 else "took damage"
changes.append(f"{direction} ({hp_change:+d} HP, now {player['hp']}/{player['max_hp']})")
if add_item:
player["inventory"].append(add_item)
changes.append(f"gained '{add_item}'")
if remove_item:
if remove_item in player["inventory"]:
player["inventory"].remove(remove_item)
changes.append(f"lost '{remove_item}'")
if use_spell_slot:
if player["spell_slots"] \> 0:
player["spell_slots"] -= 1
changes.append(f"used a spell slot ({player['spell_slots']} remaining)")
else:
changes.append("no spell slots remaining!")
return f"📋 Updated: {', '.join(changes)}" if changes else "📋 No changes made."Putting It All Together
Wire up all four tools with an expanded system prompt:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
agent = Agent(
model = BedrockModel(model_id="us.amazon.nova-2-lite-v1:0"),
system_prompt="""You are a dramatic and entertaining tabletop RPG games master.
Your responsibilities:
- Narrate scenes vividly and with atmosphere
- Use dice rolls to determine the outcomes of actions involving chance
- Consult the rule book when you need to know game mechanics
- Track the player's state (HP, inventory, spell slots) and update it after events
- Always check the player's current state before making decisions about their abilities
Game rules:
- A d20 roll of 15+ is generally a success for skill checks
- Always look up specific rules in the rule book rather than making them up
- Apply damage and healing by updating the player's state
- Be fair but dramatic. Make every roll feel consequential""",
tools=[roll_dice, lookup_rule, get_player_state, update_player_state],
)Run it again. The agent now orchestrates multiple tools in a single turn. It checks your state, consults the rulebook, rolls dice, and updates your HP, all in one response. All decided by the model.

Agent Combat!
Try things like:
- "I kick open the tavern door and demand the finest ale"
- "I challenge the biggest warrior to arm wrestling"
- "I cast a fireball at the goblin ambush!"
- "What's my current state?"
When you challenge someone to arm wrestling, the agent consults the rulebook, discovers it uses d20 + strength, rolls the dice, narrates the outcome, and updates your gold if you gambled. When you cast fireball, it checks your spell slots, rolls damage, and deducts the slot. It's running a full game.
What You Built
Take a step back and look at what happened here:
- Defined tools. Plain Python functions with docstrings that the model can call.
- Chose a model. Started local with Ollama, upgraded to Bedrock with a one-line change.
- Wrote a system prompt. Gave the model a personality and rules to follow.
- Let the agentic loop handle the rest. The SDK managed the reasoning cycle.
This is the pattern behind every AI agent, from simple chatbots to complex autonomous systems. The tools get more sophisticated (and can even be other agents), the prompts get more detailed, the models get more capable. But the core loop is always the same.
What's Next?
Try different models. Swap
us.amazon.nova-2-lite-v1:0 for us.amazon.nova-micro-v1:0 (cheapest) or us.anthropic.claude-haiku-4-5-20251001-v1:0 (stronger reasoning). Nova 2 Pro is even more capable but at the time of writing is preview / early-access only.Add more tools. Connect your agent to real APIs like weather data, databases, or web search. The
strands-agents-tools package has 30+ pre-built tools you can drop in:1
uv add strands-agents-toolsBuild multi-agent systems. Have agents delegate to other agents. A dungeon master agent could coordinate a combat agent, a lore agent, and a map agent.
Add memory. Right now your agent forgets everything when the script ends. Add a database-backed tool that saves and retrieves conversation history across sessions.
Ship it. When you're ready to go beyond your laptop, Amazon Bedrock AgentCore provides managed cloud services for deploying, scaling, and operating agents in production. It handles runtime, session management, identity, memory, and tool hosting so your agent can serve real users. That's a topic for another post.
The models keep getting better, the tools keep getting more powerful, and the free tier means you can keep experimenting.
And...
Hey! Not everything in AWS is AI! Check out this blog on connecting your app to a real database in the cloud (without losing your mind) !
Resources
- All code from this post (GitHub)
- Strands Agents SDK (Python)
- Strands Agents SDK (TypeScript)
- Amazon Bedrock Documentation
- Amazon Bedrock AgentCore
If you're building agents or just getting started with AI on AWS, I'd love to hear what you're working on. Connect with me on linkedin.com/in/mikegchambers .
This project is part of the AWS Free Tier onboarding series. Everything you built here runs within the Free Tier — no surprise bills, just learning.
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article