Every developer's first website was Hello World. Some HTML, maybe a CSS file, deploy it somewhere, and you get that rush of seeing your thing live on the internet.
With AI, the new Hello World is a chat app.
You still start with a basic index.html. But instead of static text, you wire it up to a backend AI and suddenly you've got something that actually talks back. That's what we're building today.
The stack: Strands SDK for the agent, AgentCore to host it on AWS, Bedrock for model selection, and a simple HTML frontend that hits a proxy to talk to the AI. One TypeScript file, a few CLI commands, and an index.html.
By the end, you'll have a working chat interface backed by a model on Amazon Bedrock (we'll use the ultra-cheap Amazon Nova Micro), deployed to a hosted endpoint, costing fractions of a cent per conversation. And it's the foundation for everything that comes next: memory, authentication, document Q&A, guardrails. But today we keep it dead simple.
The mental model: an agent is a model + a prompt
Before we touch code, here's what we're building:
Browser (HTML + JS) → AgentCore Runtime (hosted endpoint) → Strands Agent → Bedrock (Nova Micro)
You write a Strands agent in TypeScript. The AgentCore CLI deploys it as a hosted endpoint. Your frontend sends messages to that endpoint.
No Lambda functions. No API Gateway. No Docker. Just TypeScript and a couple CLI commands.
Step 1: Set up your environment
You'll need an AWS account, Node.js 22+, and npm.
The fast path: If you use a coding agent (Claude Code, Cursor, Kiro, Codex), the Agent Toolkit for AWS handles credentials, CLI tools, and all of the below for you. Just ask your agent to set up AWS access and it'll run through the steps.
# Your coding agent will run something like:
aws configure agent-toolkit
Or, do it manually:
Configure your AWS credentials:
# Install the AWS CLI (if you don't have it)
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip && sudo ./aws/install
# Configure your credentials
aws configure
# (enter your Access Key ID, Secret Access Key, region, output format)
# Verify it works
aws sts get-caller-identity
Once your credentials are set, install the AgentCore CLI and the AWS CDK (AgentCore uses CDK under the hood to deploy your infrastructure):
# The AgentCore CLI
npm install -g @aws/agentcore
# AWS CDK (needed for deployment)
npm install -g aws-cdk
That's your toolchain.
Step 2: Scaffold the project
One command scaffolds the entire project:
agentcore create --name MyAgent --no-agent
cd MyAgent
agentcore add agent \
--name MyAgent \
--type create \
--build CodeZip \
--language TypeScript \
--framework Strands \
--model-provider Bedrock \
--memory none
What each flag does:
-
--name– The project/agent name (alphanumeric, starts with a letter, max 36 characters). -
--build– The deployment artifact type.CodeZipmeans direct code deployment (no Docker). -
--framework– The agent framework. Supported values:Strands,LangChain_LangGraph,GoogleADK,OpenAIAgents. -
--language– The language for generated code. Supported values:TypeScript,Python. -
--model-provider– The model provider. Supported values:Bedrock,Anthropic,OpenAI,Gemini. -
--memory– Memory configuration. Supported values:none,shortTerm,longAndShortTerm.
Or skip the flags entirely and run agentcore create with no arguments. It launches an interactive wizard that walks you through each option.
You get this structure:
MyAgent/
├── agentcore/
│ ├── agentcore.json # Project config
│ └── aws-targets.json # Account + region
└── app/
└── MyAgent/
├── main.ts # Your agent ← this is the file that matters
├── model/load.ts # Which Bedrock model to use
├── package.json # Dependencies
└── tsconfig.json
Step 3: Write the agent
The scaffold gives you a working agent out of the box.
// app/MyAgent/main.ts
import { BedrockAgentCoreApp } from 'bedrock-agentcore/runtime';
import { Agent } from '@strands-agents/sdk';
import { loadModel } from './model/load.js';
const agent = new Agent({
model: await loadModel(),
systemPrompt: 'You are a helpful AI assistant. Be concise and direct.',
});
const app = new BedrockAgentCoreApp({
invocationHandler: {
async *process(payload: any) {
for await (const event of agent.stream(payload.prompt ?? '')) {
if (event.event?.delta?.type === 'textDelta') {
yield { data: event.event.delta.text };
}
}
},
},
});
app.run({ port: parseInt(process.env.PORT ?? '8080') });
That's the whole thing. ~20 lines.
Three pieces:
- Create an agent with a model and a system prompt.
-
Wire it to the server using
BedrockAgentCoreApp, which handles the HTTP endpoints the runtime expects. -
Stream tokens back by iterating over
agent.stream()and yielding each text delta.
loadModel() points at Amazon Nova Micro by default (Bedrock's cheapest text model, fractions of a cent per conversation). Swap the model ID in model/load.ts if you want Claude Sonnet or something heavier. The full version in the repo adds per-session caching so each conversation keeps its own history, but this is all you need to get a response streaming.
Step 4: Test locally
Before deploying anywhere, run it on your machine:
agentcore dev
In a separate terminal:
agentcore dev "What is the capital of France?"
This installs dependencies, compiles TypeScript, and starts a local server on port 8080. It also opens a browser-based inspector where you can chat with your agent. You should see the response stream back in the second terminal. If that works, your agent runs.
First run takes a moment while it pulls packages and compiles. After that it's near-instant with hot reload.
Step 5: Deploy to AWS
One command:
agentcore deploy
The CLI:
- Compiles TypeScript and packages it as a CodeZip archive
- Uses CDK to synthesize and deploy CloudFormation (Infrastructure as Code) resources
- Creates IAM roles and an AgentCore Runtime endpoint
- Configures CloudWatch logging
First deploy takes a few minutes while CDK bootstraps your account. Subsequent deploys are faster. You can run agentcore deploy --dry-run to preview changes without deploying.
Test your deployed agent:
agentcore invoke "Hello! What can you help me with?"
Stream the response in real time:
agentcore invoke "Tell me a joke" --stream
If you see a response, your agent is live on AWS.
Step 6: The frontend
One HTML file. No build step. No npm install.
During local development, this frontend talks to your dev server (agentcore dev on port 8080). For production, the repo includes a Lambda proxy that handles SigV4 signing so browsers can reach the deployed agent. Step 7 covers that.
The core of the frontend is one function:
// Call your agent and stream the response
async function send(text, onToken) {
const res = await fetch("http://localhost:8080/invocations", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "text/event-stream", // required: the agent streams SSE
"X-Amzn-Bedrock-AgentCore-Runtime-Session-Id": sessionId, // keeps chat history
},
body: JSON.stringify({ prompt: text }),
});
// The response is a Server-Sent Events stream: `data: "token"` frames.
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const events = buffer.split("\n\n");
buffer = events.pop(); // keep any partial frame for the next read
for (const evt of events) {
const line = evt.split("\n").find((l) => l.startsWith("data:"));
if (line) onToken(JSON.parse(line.slice(5).trim()));
}
}
}
Two things bit me here, so they're worth calling out. The server requires the Accept: text/event-stream header. Without it, you get a JSON error object instead of a stream. And the response isn't a single JSON blob. It's an SSE stream of JSON-encoded token strings. In exchange, you get ChatGPT-style token-by-token rendering for free.
The rest is just HTML and CSS to make it look like a chat window. The full index.html (dark theme, chat bubbles, enter-to-send) is in the repo. Clone it, run agentcore dev, open the file in your browser. That's your ChatGPT.
# Grab the frontend
git clone https://github.com/tmoreton/tutorials
open tutorials/index.html
Step 7: Deploy the frontend with GitHub Pages
Push your repo to GitHub:
git init
git add .
git commit -m "initial commit"
git remote add origin git@github.com:YOUR_USERNAME/my-ai-chat.git
git push -u origin main
Then go to your repo on GitHub, click Settings > Pages, set the source to "Deploy from a branch", pick main and / (root), and hit Save:
Give it a minute and your app is live at https://YOUR_USERNAME.github.io/my-ai-chat. HTTPS, free hosting, auto-deploys on every push.
The repo includes a small streaming Lambda proxy behind CloudFront (proxy/) that signs requests to the AgentCore Runtime on behalf of the browser. The hosted frontend talks to this proxy instead of localhost. Deploy it with cd proxy && cdk deploy and update the ENDPOINT constant in index.html to your CloudFront URL.
The CloudFront OAC setup has a couple wrinkles: the x-amz-content-sha256 header and the double invoke permission. The proxy README walks through both. That cost me an afternoon so you don't repeat it.
A few things to keep in mind
The response is a stream, not JSON. The agent server speaks Server-Sent Events and requires the Accept: text/event-stream header. If your frontend tries res.json() expecting a neat {result: "..."} object, you'll get an error back instead. You need to read the stream (see Step 6).
The deployed endpoint is only reachable through the proxy. AgentCore requires SigV4-signed requests, which browsers can't do natively. That's what the Lambda proxy in proxy/ handles for you. Don't try to sign from client-side JS. You'd be shipping AWS credentials in page source.
Limit concurrent calls. This endpoint is unauthenticated, which means anyone with the URL can hit it. Set a concurrency limit on the Lambda proxy or add throttling in CloudFront so you don't wake up to a surprise bill.
The full picture
| Layer | What | How |
|---|---|---|
| Model | Amazon Nova Micro (swappable) | Amazon Bedrock |
| Agent | ~40 lines of TypeScript | Strands Agents SDK + AgentCore app server |
| Backend hosting | agentcore deploy |
AgentCore Runtime |
| Frontend hosting | Push to GitHub | GitHub Pages + CloudFront proxy |
Right now conversation history lives in memory, so it disappears if the process restarts. Next post, we'll add AgentCore Memory for durable persistence across sessions and restarts.
Clean up
If you want to tear down the AWS resources when you're done:
agentcore remove all
agentcore deploy
The first command resets your config. The second deploys the empty state, which tears down the CloudFormation stack.
Source
The complete code for this tutorial is on GitHub →
If this was useful, follow along. The next post drops next week.

Top comments (0)