Build an AI Agent in 10 Minutes
Build a TypeScript AI calendar agent with Strands Agents and Claude Haiku 4.5 on Amazon Bedrock in about 10 minutes. It books, moves, checks and cancels meetings in plain English, and it never double-books, because the overlap check lives in the tool's code, not just the prompt. You'll set up least-privilege IAM, run a one-command Bedrock pre-flight check, clone and test the app, watch it refuse a real clash, and fix the six errors you're most likely to hit. Part 1 of 3.
Series: Strands Agents on Amazon Bedrock: Build, Understand and Ship a TypeScript AI Calendar Agent That Never Double-Books (3 articles)
- 1Build an AI Agent in 10 Minutes This article
Hey Builders!
Ask most agent demos to book two meetings at 3pm and they'll do it, then confirm both politely. The model called a tool, the tool ran, and your calendar is now wrong. The model isn't the problem. The problem is that "never double-book" only lived in the prompt.
In this series we build a calendar assistant with Strands Agents for TypeScript and Claude on Amazon Bedrock . It books, moves, checks and cancels appointments in plain English. When a slot is taken, the tool refuses to save, tells the model what's in the way and which times are free, and the model asks you what to do. The code is on GitHub: schinchli/strands-calendar-agent (Apache-2.0).
In this part
- See the agent handle a real clash on Bedrock.
- Set up Bedrock access with a least-privilege IAM policy and a one-command pre-flight check.
- Clone, test and run the agent in about 10 minutes, with a checkpoint every few steps.
- Fix the six errors you're most likely to hit.
See the Strands agent refuse a double-booking
Here's the whole flow, running live on Amazon Bedrock:
- Start. The header shows the model, region, calendar file and today's date.
- Book. "Book 'Agent fun' for tomorrow 3pm in NYC." The agent calls
create_appointmentand returns an ID. - Move. "It's actually in DC." The agent reuses the ID and changes only the location.
- Clash. "Also book 'Design review' tomorrow at 3pm." The agent calls
check_availability, names the clash, lists only free times and asks. Nothing is saved. - Answer. "4pm works." Booked.
- Check.
/calendarreads SQLite directly: two meetings, no overlap.

After setup, the header confirms the settings and prints the three requests used in the demo.

The clash turn: the agent checked availability, saved nothing, offered only free times, and waits for you.

Turn 1: "tomorrow 3pm" becomes
2026-10-07 15:00, and the tool returns an ID.
Turn 2: the agent reuses the ID and changes only the location.

Turn 4: you choose, and only then does the booking happen.
Why I built it, and the mistake that shaped it
I started from the
02-custom-tools TypeScript sample in strands-agents/samples . It booked whatever the model asked for, and it no longer started, because it installed the SDK from an archived GitHub repository.My first version put the rules in the system prompt. It worked most of the time. Then I ran the clash scenario repeatedly and read every transcript. Two failures kept coming back:
- The model remembered the 3pm meeting from earlier in the chat, skipped the tools, and answered from memory.
- When a tool returned the whole day's free slots, the model sometimes "helpfully" added a time that wasn't in the list, such as 2:30pm next to a 3–4pm meeting.
The fix was to make the tool enforce the rule. It checks overlaps in code, saves nothing on a clash, and returns exactly three free times.
I also made a mistake of my own. My first write-up said the agent "always asks before deleting". A trace showed the model calling
delete_appointment with confirmed: true right after the user said yes. Correct behaviour, but the model sets that flag, so code can't guarantee it. I corrected the docs and the upstream pull request.That's the idea the whole series rests on: a tool can return a decision, not just an error. Part 2 shows exactly how.
How Strands Agents call tools on Amazon Bedrock
Your code builds a Strands
Agent with a Bedrock model and five tools, reads lines from the terminal, and calls agent.invoke() once per turn. Strands runs the loop: it sends the conversation and tool descriptions to the model, runs the tool the model picks, returns the result, and repeats until the model answers.
create_appointment: Books a meeting unless it overlaps one. On a clash it saves nothing and returns the clash and three free times.list_appointments: Lists everything booked.update_appointment: Changes a meeting, with the same overlap check when the time moves.check_availability: Lists free start times on a day, within working hours.delete_appointment: Deletes only when called withconfirmed: true.
Prerequisites: Node.js, AWS and Bedrock model access
- Node.js 22 or later. The SDK requires it, and
package.jsondeclares"node": ">=22". - An AWS account with credentials in your shell (a named profile, SSO session or role). Avoid long-lived access keys where you can.
- Amazon Bedrock access to Claude Haiku 4.5. Bedrock enables serverless models on first use if your identity has AWS Marketplace permissions. Anthropic models also need a one-time first-time-use form per account. See Access Amazon Bedrock foundation models .
- The AWS CLI v2, for the pre-flight check.
Attach this policy to the identity that runs the agent. It covers the three Bedrock calls the Strands provider makes:
ConverseStream (authorized by bedrock:InvokeModelWithResponseStream), Converse (bedrock:InvokeModel) and the optional CountTokens. A cross-region inference profile needs both the profile ARN and the foundation model ARN in each Region it routes to. See Prerequisites for inference profiles .1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "InvokeClaudeHaiku45",
"Effect": "Allow",
"Action": [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream",
"bedrock:CountTokens"
],
"Resource": [
"arn:aws:bedrock:*:<YOUR_ACCOUNT_ID>:inference-profile/us.anthropic.claude-haiku-4-5-20251001-v1:0",
"arn:aws:bedrock:*::foundation-model/anthropic.claude-haiku-4-5-20251001-v1:0"
]
}
]
}We checked this policy with the IAM policy simulator: it allows the Haiku profile and the Haiku model in another US Region, and implicitly denies other models such as Claude Sonnet. If your organization requires it, replace the
* Regions with the Regions the profile routes to.Run this pre-flight check before you touch any code. It separates "my AWS setup is wrong" from "my code is wrong":
1
2
3
4
5
6
7
node --version # v22 or later
aws sts get-caller-identity --query Arn --output text
aws bedrock-runtime converse --region us-east-1 \
--model-id us.anthropic.claude-haiku-4-5-20251001-v1:0 \
--messages '[{"role":"user","content":[{"text":"Reply with the single word: ready"}]}]' \
--query 'output.message.content[0].text' --output text
# readyIf the last command prints
ready, Bedrock, the model and your permissions are all in place.Build the TypeScript AI agent in 10 steps
Step 1. Clone the repository.
1
2
git clone https://github.com/schinchli/strands-calendar-agent.git
cd strands-calendar-agentStep 2. Install dependencies from the lockfile. On a clean machine this takes a few seconds.
1
npm ciStep 3. Run the tests. They need no AWS credentials.
1
npm test✅ Checkpoint: the summary shows
ℹ pass 9 and ℹ fail 0. If npm ci printed EBADENGINE, switch to Node.js 22 or later before going further.Step 4. Run the pre-flight check from Prerequisites and confirm it prints
ready.Step 5. See every option.
1
npm run dev -- --help
--help lists every mode, setting, environment variable and default.Step 6. Choose your settings. Setup asks for each value, rejects invalid ones, and saves
assistant.config.json.1
npm run setup✅ Checkpoint:
cat assistant.config.json shows your Region, model and time zone, and git status does not list the file (it's in .gitignore).
Setup validates each answer and asks again when one is invalid.
Step 7. Run the guided demo. It sends the three sample requests, including the clash, then hands the chat to you.
1
npm run devStep 8. Answer the clash. When the agent offers free times, pick one, for example
4pm works.Step 9. Check what was saved. Type
/calendar, then /config.✅ Checkpoint:
/calendar lists exactly two appointments with no overlap, and each create_appointment call appears as a 🔧 Tool line in the transcript.Step 10. Keep chatting. Try "What's on tomorrow?", "Move Design review to 5pm" or "Cancel Agent fun". Type
exit to quit.Try this. Push it. After booking 'Agent fun' at 3pm, ask: "Book a call tomorrow at 3pm, I don't mind overlapping", then "Yes, book it anyway". When I tried this, the model refused both times, and look closely: no
🔧 Tool line appeared. It refused from memory. This time the prompt did the work. That's fine, until the run where the model forgets. The booking tool is the backstop for that run, and Part 2 shows the three lines that make it one.Fix common Amazon Bedrock and Strands errors
These are real errors I hit while building and testing, with the exact messages.
ModelError: Invocation of model ID anthropic.claude-haiku-4-5-20251001-v1:0 with on-demand throughput isn’t supported. Retry your request with the ID or ARN of an inference profile that contains this model.You passed the base model ID. Claude Haiku 4.5 on demand is called through an inference profile: use
us.anthropic.claude-haiku-4-5-20251001-v1:0 (or global.…).ModelError: The provided model identifier is invalid.The model ID is misspelled, or not offered in the Region you set. Check
/config for the model and Region, and list what's available with aws bedrock list-inference-profiles --region <REGION>.ModelError: The security token included in the request is invalid.Your credentials are wrong or expired. Run
aws sts get-caller-identity; for SSO, run aws sso login again.Invalid settings: followed by a list, and the app exits with code 11
2
3
4
5
6
Invalid settings:
region "mars" does not look like an AWS region (e.g. us-east-1)
timezone "Mars/Base" is not a known IANA time zone (e.g. Asia/Kolkata, America/New_York)
duration "0" must be a whole number of minutes between 5 and 1440
workday "18-09" must be start-end hours, e.g. 09-18
Run with --help, or --setup to choose settings interactively.Every problem is listed at once, before any Bedrock call. Fix the values, or run
npm run setup.
npm warn EBADENGINE Unsupported engineYou are on Node.js 20 or earlier. Install Node.js 22 or later, then delete
node_modules and run npm ci again. better-sqlite3 is a native module, so it must be rebuilt for the Node.js version you run it with.Cannot find package …/@strands-agents/sdk/index.jsYour
package.json installs Strands from the archived github:strands-agents/sdk-typescript. Depend on the npm package @strands-agents/sdk instead.Clean up
Nothing runs in AWS after you exit, so clean-up is local plus IAM:
1
2
rm -f appointments.db assistant.config.json # calendar data and saved settings
cd .. && rm -rf strands-calendar-agent # the cloneIf you created an IAM user or policy only for this walkthrough, detach and delete it, and delete any access keys you created. Model access in Bedrock has no standing charge, so you can leave it enabled.
What you learned
- Strands keeps your code small. You build an
Agent, give it tools, and callinvoke()once per turn. Strands runs the loop. - Check the AWS side first. A pre-flight
conversecall separates IAM and model-access problems from code problems. - Prompts guide; tools enforce. The model usually follows the prompt. When it doesn't, the booking tool still refuses the clash, and that's the guarantee you can rely on.
Up next
You've seen what it does. In Part 2: Tools the model can't break, we open the nine pieces of code that make it work. You'll see the one comparison that defines a clash, the small JSON report that turns a refusal into a question, and the honest limit of the delete tool.
Series: Strands Agents on Amazon Bedrock: Build, Understand and Ship a TypeScript AI Calendar Agent That Never Double-Books (3 articles)
- 1Build an AI Agent in 10 Minutes This article
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article