Instructor
Run the room
One account stack serves the whole class, organised by company: the LiteLLM gateway, participant roles, budgets, guardrails and this website. Model access uses server-side virtual keys that are never handed out: the gateway recognises each participant by IAM role.
Sample one-day agenda
| Time | Session | Pages |
|---|---|---|
| 09:00 | Welcome, story and architecture | Home page |
| 09:20 | Setup: clone, SSO, llm, phase1 t0 | Phase 1 · Task 0 |
| 09:50 | Prompts, MCP tools, sessions, context windows | Phase 1 · Tasks 1–4 |
| 11:00 | Break | |
| 11:15 | Hooks, structured output, agents as tools | Phase 1 · Tasks 5–7 |
| 12:30 | Lunch (run up 2 before you go) | |
| 13:30 | Identity, MCP runtime, Gateway, Memory | Phase 2 · Tasks 0–3 |
| 14:30 | Deploy and personalise the agent | Phase 2 · Task 4 |
| 15:30 | Break | |
| 15:45 | Observability, evaluations, Cedar policy | Phase 2 · Tasks 5–7 |
| 16:45 | Optional add-ons, wrap-up, down | Optional pages |
Provisioning takes minutes, so start up N before breaks. Anyone behind can catch up with up N.
Set up the class
List companies and participants
Edit
roster.json. Participants are grouped by company underteams. Each company becomes a LiteLLM teambootcamp-<company>whosebudget_usdcaps what its participants spend together. Each participant getsdefault_budget_usdunless they set their ownbudget_usd; optionally addprincipal_arnto restrict who may assume their role. Participant roles are taggedTeam=<company>as well asParticipant=<name>.roster.json{ "default_budget_usd": 1, "dns_zone": "workshop.example.com", "dns_prefix": "bootcamp", "teams": { "acme": { "budget_usd": 20, "participants": { "alice": {}, "bob": {"budget_usd": 2} } }, "globex": { "budget_usd": 15, "participants": {"carol": {}} } }, "alert_email": "instructor@example.com", "infra_limits": {"enforce": true, "monthly_budget_usd": 100, "per_participant": {"code_interpreter_sessions": 3, "browser_sessions": 3}} }Bring up the account stack (once)
terminaluv run bootcamp.py account upDeploys the shared platform, creates a
bootcamp-participant-<name>role per participant and syncs their server-side virtual keys and budgets. Adding a participant later also needsaccount up, because it creates their IAM role.Sync server-side virtual keys and budgets
terminaluv run bootcamp.py account keysOnly syncs LiteLLM virtual keys and budgets with
roster.json, for example to top up someone who ran out. Keys stay on the server and are never handed out: the gateway maps each IAM role to its key.Hand out names and SSO details
Give each participant their
PARTICIPANTname, the SSO start URL and region (see below), and point them at this site. The CLI derives their role;BOOTCAMP_ROLE_ARNis only an optional override.Watch spend during the day
terminaluv run bootcamp.py account spendPrints spend per company team, then per participant.
Tear down
Ask participants to run
uv run bootcamp.py down, then check for leftovers and remove the account stack:terminaluv run bootcamp.py account purge # dry run: lists leftover participant resources uv run bootcamp.py account purge --force # deletes them (only if the list wasn't empty) uv run bootcamp.py account downaccount downrefuses while any participant resources still exist and lists them;account down --forcepurges first, then destroys.
Participant AWS access (SSO)
Participants sign in with AWS IAM Identity Center: share the SSO start URL and region; they run aws configure sso and put the profile in AWS_PROFILE. The CLI then assumes bootcamp-participant-<name>.
Each role's trust defaults to the account root, so the participants' permission set must allow sts:AssumeRole on arn:aws:iam::<account>:role/bootcamp/bootcamp-participant-*. To pin a role to one person, set principal_arn for that participant in roster.json.
Infra guardrails
LLM spend is capped by LiteLLM: per-participant budgets inside per-company team budgets (HTTP 429 budget_exceeded). AWS spend is watched by an infra guard Lambda, CloudWatch alarms and an AWS Budget, all alerting to the SNS topic bootcamp-alerts.
Per-participant limits
The guard reacts within seconds to new Code Interpreter / Browser sessions (a dedicated CloudTrail trail, bootcamp-agentcore-sessions, logs only those two session-start data events; nothing else in the sandbox is logged) and sweeps live resources every 5 minutes. It attributes usage by caller role (bootcamp-participant-<p> / awsworkshop-<p>-*) and resource name. Alerts are de-duplicated per hour.
Limit (infra_limits.per_participant) | Default | When exceeded |
|---|---|---|
runtimes | 4 | alert |
gateways | 2 | alert |
memories | 2 | alert |
code_interpreters | 2 | alert |
browsers | 2 | alert |
code_interpreter_sessions | 3 | auto-stop newest (if enforce) |
browser_sessions | 3 | auto-stop newest (if enforce) |
runtime_invocations_5min | 300 | alert |
Override per team (teams.<t>.infra_limits) or per participant (teams.<t>.participants.<p>.infra_limits). Class-wide totals default to per-participant × headcount (override with infra_limits.class). Auto-stop needs infra_limits.enforce: true; runtimes, gateways and memories are alert-only.
Alarms
CloudWatch alarm → bootcamp-alerts | Threshold |
|---|---|
| Code Interpreter ActiveSessionCount (account) | 25 |
| Browser ActiveSessionCount (account) | 25 |
| Runtime ActiveSessionCount (account) | 200 |
| InvokeAgentRuntime (account, 5 min) | 1500 |
| LiteLLM gateway ECS CPU | > 85% |
| LiteLLM RDS CPU | > 80% |
| Guard Lambda errors | any |
The 5-minute sweep also reports when the LiteLLM gateway autoscaling is at its max task count.
Cost
AWS Budget bootcamp-genai-bootcamp-tagged on cost tag Project=genai-bootcamp (infra_limits.monthly_budget_usd, default $100), notifying at 80% actual and 100% forecast. AWS-managed tool sessions can't be tagged, so set infra_limits.account_budget_usd > 0 if you also want an account-wide budget.
Manual steps
- Set
alert_emailinroster.jsonand runuv run bootcamp.py account up. - Confirm the SNS subscription email. Until then nobody is emailed.
- Activate
Projectas a cost allocation tag in the payer account. - Optional billing alarm: enable billing alerts, put
enable_billing_alarm = trueinterraform/account/terraform.tfvars, then runaccount up.
What account up creates and common issues
- LiteLLM gateway on ECS Fargate (2 tasks) behind CloudFront with a 120 s origin timeout, the only path to models. Participants call it with an OpenAI-compatible API and a presigned STS identity header (
X-Amz-Sts-Identity-Url); it mapsbootcamp-participant-<name>andawsworkshop-<name>-agent-runtimeto that participant's key and forwards to Bedrock. - RDS database for LiteLLM keys, budgets and spend tracking. The gateway URL is stored per participant in the secret
bootcamp/participants/<name>/litellm, from which the CLI fills inLITELLM_BASE_URL. - Per-participant IAM roles with tag-based isolation (
Participant=<name>,Team=<company>, namesawsworkshop-<name>-*) and a permissions boundary on every role they create that blocks direct Bedrock access. Participants can't assume their workload roles, and every role they create must carry thebootcamp-workload-boundary(an allowlist) and theirParticipanttag; the platform adds both automatically. - Observability:
account upleaves the account's Transaction Search setting alone; runuv run bootcamp.py account up --transaction-searchto switch it on so participants' traces show up in GenAI Observability (Phase 2, Task 5). - This website, served from CloudFront.
Common issues: the first up 0 waits about 5 minutes for Cognito DNS (domains look like ds-<10 hex>-<account>); if someone queried the domain too early, flush their DNS cache. account up warns loudly when alert_email is empty. Stage 5 checks may WARN for about 10 minutes until spans arrive. A participant whose own budget or company team budget is exhausted gets HTTP 429 budget_exceeded from LiteLLM: check account spend and raise their budget with account keys.