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

TimeSessionPages
09:00Welcome, story and architectureHome page
09:20Setup: clone, SSO, llm, phase1 t0Phase 1 · Task 0
09:50Prompts, MCP tools, sessions, context windowsPhase 1 · Tasks 1–4
11:00Break
11:15Hooks, structured output, agents as toolsPhase 1 · Tasks 5–7
12:30Lunch (run up 2 before you go)
13:30Identity, MCP runtime, Gateway, MemoryPhase 2 · Tasks 0–3
14:30Deploy and personalise the agentPhase 2 · Task 4
15:30Break
15:45Observability, evaluations, Cedar policyPhase 2 · Tasks 5–7
16:45Optional add-ons, wrap-up, downOptional pages

Provisioning takes minutes, so start up N before breaks. Anyone behind can catch up with up N.

Set up the class

  1. List companies and participants

    Edit roster.json. Participants are grouped by company under teams. Each company becomes a LiteLLM team bootcamp-<company> whose budget_usd caps what its participants spend together. Each participant gets default_budget_usd unless they set their own budget_usd; optionally add principal_arn to restrict who may assume their role. Participant roles are tagged Team=<company> as well as Participant=<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}}
    }
  2. Bring up the account stack (once)

    terminal
    uv run bootcamp.py account up

    Deploys 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 needs account up, because it creates their IAM role.

  3. Sync server-side virtual keys and budgets

    terminal
    uv run bootcamp.py account keys

    Only 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.

  4. Hand out names and SSO details

    Give each participant their PARTICIPANT name, the SSO start URL and region (see below), and point them at this site. The CLI derives their role; BOOTCAMP_ROLE_ARN is only an optional override.

  5. Watch spend during the day

    terminal
    uv run bootcamp.py account spend

    Prints spend per company team, then per participant.

  6. Tear down

    Ask participants to run uv run bootcamp.py down, then check for leftovers and remove the account stack:

    terminal
    uv 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 down

    account down refuses while any participant resources still exist and lists them; account down --force purges 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)DefaultWhen exceeded
runtimes4alert
gateways2alert
memories2alert
code_interpreters2alert
browsers2alert
code_interpreter_sessions3auto-stop newest (if enforce)
browser_sessions3auto-stop newest (if enforce)
runtime_invocations_5min300alert

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-alertsThreshold
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 errorsany

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

  1. Set alert_email in roster.json and run uv run bootcamp.py account up.
  2. Confirm the SNS subscription email. Until then nobody is emailed.
  3. Activate Project as a cost allocation tag in the payer account.
  4. Optional billing alarm: enable billing alerts, put enable_billing_alarm = true in terraform/account/terraform.tfvars, then run account 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 maps bootcamp-participant-<name> and awsworkshop-<name>-agent-runtime to 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 in LITELLM_BASE_URL.
  • Per-participant IAM roles with tag-based isolation (Participant=<name>, Team=<company>, names awsworkshop-<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 the bootcamp-workload-boundary (an allowlist) and their Participant tag; the platform adds both automatically.
  • Observability: account up leaves the account's Transaction Search setting alone; run uv run bootcamp.py account up --transaction-search to 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.