Shipping Relay: Serverless Deployment, Enterprise Integration, and CI/CD (AWS GenAI Developer Pro, Module 11)

Module 11 of 16 17 min read D2 · 26% Lab code ↗

This is Module 11 of AWS GenAI Pro Mastery, a free 16-module course that takes you from your first Amazon Bedrock call to AWS-certified.

CloudCart has a home-grown ticketing system, Zendesk-style, and the team wants to plug Relay into it: “When a ticket arrives, call Relay, show the customer the answer, and if Relay escalates, drop the ticket into the human queue.” Reasonable ask, with no way to do it. The Relay you built through Module 10 is safe and governed but has no front door: Module 8 stood it up on AgentCore Runtime, yet there is no public API a ticketing system can call, no async pipeline, and no CI/CD. You still invoke it by hand with uv run python -m relay.run; a SaaS cannot. Worse, Module 8’s refund approval gate has waited three modules for a POST /tickets/{id}/approve endpoint that does not exist. A GenAI system with no front door is a demo, not a product. By the end of this module, POST /tickets answers from the public internet, and every commit redeploys Relay safely.

In this module

You’ll learn how to:

  • Choose a foundation model (FM) deployment strategy — on-demand vs provisioned throughput vs SageMaker AI endpoints — by throughput, latency, and idle cost (skills 2.2.1–2.2.3).
  • Expose Relay behind API Gateway + AWS Lambda with request validation and asynchronous processing over Amazon SQS (skills 2.4.1, 2.3.2).
  • Integrate through loose coupling: event-driven architecture on Amazon EventBridge, webhooks, and legacy connectors (skill 2.3.1).
  • Secure the front door — authenticate the API’s consumers with Cognito, identity federation, and RBAC, distinct from Module 10’s internal IAM (skill 2.3.3).
  • Codify the infrastructure in AWS CDK and automate the deploy with a CodePipeline (skill 2.3.5).
  • Survey the surrounding tooling — Amplify and OpenAPI (skill 2.5.2) — and what Amazon Q Developer adds to the dev loop (skill 2.5.4), without deploying either.

You’ll build: POST /tickets (API Gateway + Lambda, async via SQS) and GET /tickets/{id}, escalations on the EventBridge relay-events bus, the whole stack in AWS CDK, and a CodePipeline with smoke tests and rollback.

Exam domains covered: D2 — Implementation and Integration (26% of the exam). Modules 7 and 8 covered the agents half of D2; this module covers the deployment, enterprise-integration, and CI/CD half — Tasks 2.2, 2.3, and 2.5, plus skill 2.4.1. D2 is the second-largest domain (behind D1’s 31%), and this module is its densest block.

Prerequisites: Modules 1–10. An AWS_PROFILE that resolves to your course account, us-east-1 everywhere. You need the Module 8 deployed agent and its HITL gate, the Module 7 tables and schemas, and the Module 10 iam/policies/.

Where you are in the build

One stop on a 16-module road:

  • ✅ M1–M10 — triage, the converse() router, RAG and a cited Knowledge Base, multimodal intake, a Strands agent, an AgentCore deployment with a HITL gate, a guardrail, and a security review.
  • 👉 M11 — Relay gets a front door. A serverless API, async processing, event-driven escalations, the stack in CDK, and a CI/CD pipeline.
  • ⬜ M12–M16 — cost, evaluation, observability, and the capstone.

Relay before this module: safe and governed, and deployed on AgentCore Runtime (M8) — but headless: no public API, no async queue, no CI/CD pipeline. Relay after: behind a serverless API, processed async through a queue, escalating over an event bus, described in code, redeployed by a pipeline that smoke-tests before it promotes. A real service. (Cutting per-ticket cost is Module 12.)

Deployment strategies: on-demand, provisioned throughput, SageMaker endpoints

Before you build a front door, decide how the model behind it is served — the exam tests this trade-off hard. Bedrock gives you two serving modes; a third lives outside it.

On-demand is the default. You call converse(), pay per token, and pay nothing when no one is calling — what Relay and most GenAI workloads should use. Throughput is shared and quota-bound, exactly right for bursty, unpredictable support traffic.

Provisioned throughput buys guaranteed throughput: you reserve model units in advance, on an hourly or monthly commitment, for a dedicated token rate quotas cannot throttle. The catch — you pay for those units whether or not you send a single token, billed idle. It is the answer for “a guaranteed throughput SLA at high, steady volume,” not “we’re going live.”

SageMaker AI endpoints are the third option, outside Bedrock — a custom or open-weight model served from your own container on an instance billed per hour while it exists. Most control, most cost.

The comparison the exam keeps coming back to:

On-demand (Bedrock)Provisioned throughput (Bedrock)SageMaker AI endpoints
Idle cost$0 — pay per token onlyBilled idle — reserved model unitsBilled idle — per instance-hour
Throughput guaranteeShared, account quotasGuaranteed (reserved model units)Sized by instance count
Latency profileVariable, can throttle under quotaPredictable, reservedPredictable, dedicated instance
Best forBursty / unpredictable / devHigh, steady volume; SLA; some custom modelsCustom/open-weight models, full container control
Relay uses it?YesNo (theory only)No (theory only)

Relay is on-demand, and so is this lab — no provisioned throughput or SageMaker endpoint in the code, since both are idle-billed exam scenarios, not stood up. Model cascading (skill 2.2.3) — easy tickets to a small fast model, hard ones to a bigger one — is the fast/smart router from Module 3, the software lever complementary to this capacity choice; the dollar math of provisioned versus on-demand is Module 12.

The SageMaker lane hides the decisions the exam tests under skill 2.2.2 (LLM-specific deployment) — the part Bedrock makes disappear. Two distinct angles, both theory:

🧠 Theory — skill 2.2.2, angle 1: container serving and model loading. A self-served LLM is a container you run, not an API you call: you pick a serving stack (SageMaker’s Large Model Inference container, vLLM/TGI behind it), and the weights load into GPU memory at container start, not per request. That cold load is the catch — a multi-billion-parameter checkpoint takes tens of seconds to minutes before the endpoint serves a token — and memory is the hard ceiling: weights plus the KV cache for in-flight tokens must fit in VRAM, which caps concurrent requests and context length. Bedrock on-demand erases all of it: the model is always loaded, capacity is AWS’s problem — the convenience you trade away to serve a custom checkpoint.

🧠 Theory — skill 2.2.2, angle 2: GPU sizing and cold-start. Sizing a self-served endpoint is a GPU-fit problem before a throughput one: you pick an accelerator family and count (ml.g5/ml.g6 for a small model, ml.p5-class for a large one) — too small fails to load, too large is idle burn on an instance billed per hour it exists. The matching pain is cold-start at scale: a new instance must boot and reload weights before serving, so auto-scaling reacts in minutes, not the milliseconds Lambda gives you. Bedrock skips this surface — on-demand has no instance to size, provisioned throughput buys a capacity floor without you touching a GPU.

⚠️ Common misconception: “Production means provisioned throughput.” False, and a classic D2↔D4 cost trap. On-demand covers most production GenAI workloads and costs nothing at idle; provisioned throughput exists to guarantee an SLA at scale and bills the reserved units around the clock. Default to on-demand; reserve only when a real SLA demands it.

A front door for Relay: API Gateway, Lambda, and async with SQS

The naive design is synchronous: POST /tickets calls the agent, waits, returns the answer. It does not work. A Relay run is a multi-turn ReAct loop — triage, retrieve, tool calls, draft, guardrail — taking several seconds, sometimes more than ten. The HTTP client times out, API Gateway caps a synchronous integration at 29 seconds, and a burst piles up behind held connections. This is the sync vs async decision skill 2.4.1 is built on: decouple accepting work from doing it.

So Relay’s front door is asynchronous. POST /tickets does only the fast part — validate, persist, enqueue — and returns HTTP 202 Accepted (not 200: the work is accepted, not done) with a {ticket_id} immediately; a worker Lambda drains the queue, runs the agent, and writes the final TicketRecord, which the client polls GET /tickets/{id} for. The glue is Amazon SQS, absorbing the burst and draining at the workers’ rate.

The whole flow:

flowchart LR
    C["CloudCart client"] -->|"POST /tickets"| AG["API Gateway<br/>(request validation)"]
    AG --> P["post Lambda<br/>202 {ticket_id}"]
    P -->|"write status:received"| DB[("relay-tickets<br/>DynamoDB")]
    P -->|"enqueue job"| Q["Amazon SQS<br/>work queue (+ DLQ)"]
    Q --> W["worker Lambda"]
    W -->|"frozen run_relay contract<br/>(in-process in the lab,<br/>InvokeAgentRuntime in prod)"| AGENT["Relay agent (M8)<br/>via run_relay"]
    AGENT -->|"final TicketRecord"| DB
    C -->|"GET /tickets/{id}"| AG2["API Gateway"]
    AG2 --> G["get Lambda"]
    G -->|"read"| DB

Four Lambda handlers, one per job — post, worker, get, approve — and the worker invokes the agent through the frozen Module 8 run_relay contract, never re-implementing it. In this lab the worker calls run_relay in-process (one fewer hop, and it sidesteps needing the AgentCore Runtime container in the course account); in production the identical contract is served by InvokeAgentRuntime against the Module 8 deployment. Either way the worker reuses the contract — the contract, not the host, is the stable seam, which is exactly why re-implementing the agent inline is the wrong answer.

Validation happens twice, on purpose (skill 2.4.1). API Gateway runs a JSON-Schema request model that rejects a malformed payload before a Lambda cold-starts; then post_handler enforces the business contract — a non-empty customer_message, a valid channel — returning a clean 400, never a stack trace.

One exam plant to disarm: “serverless can’t stream.” False — API Gateway WebSocket APIs and Lambda response streaming both deliver token-by-token output over a serverless stack, on the same ConverseStream from Module 3; the async-poll design here is a latency-decoupling choice, not a streaming limit.

Event-driven and enterprise integration

CloudCart’s request had a tail: “if Relay escalates, drop the ticket into the human queue.” The wrong way is the worker calling CloudCart’s human-queue API directly — coupling Relay to a system it should not know about. The right way is loose coupling: Relay announces what happened and stays ignorant of who cares.

The mechanism is Amazon EventBridge, a serverless event bus that decouples publishers from consumers. Relay publishes to a custom bus, relay-events, with two detail-types: relay.escalation when the agent hands a ticket to a human, and relay.approval_required when a refund is parked — one PutEvents, and the worker moves on. Rules on the bus match a detail-type and route the event to targets (below). The lab wires a rule for relay.escalation (to a human-escalation SQS queue); relay.approval_required is published for future consumers, and adding its rule and target is left as your turn — because a new consumer is a new rule, not a code change.

flowchart LR
    W["worker Lambda<br/>PutEvents"] --> BUS["EventBridge bus<br/>relay-events"]
    BUS --> R1["rule:<br/>relay.escalation"]
    BUS -. "event published;<br/>rule is your turn" .-> R2["rule:<br/>relay.approval_required"]
    R1 --> HQ["human escalation<br/>SQS queue"]
    R2 -. .-> AI["approval inbox<br/>(target)"]
    R1 -. "add a target,<br/>not a code change" .-> WH["outbound webhook<br/>(Lambda)"]

Connecting an FM workload to existing enterprise systems (skill 2.3.1) leans on three serverless patterns: API-based integrations wrap a legacy system behind a Lambda the agent calls as a tool — how Module 7’s MCP server fronts CloudCart’s order book; webhooks push the other way, an EventBridge rule triggering a Lambda on relay.escalation that POSTs to an external URL; and data synchronization keeps a consumer’s mirror consistent as events fire.

One concept rounds out skill 2.3.5: the GenAI gatewaynot an AWS product but a pattern, a single governed access layer in front of your foundation models that centralizes authentication, consumer throttling, usage tracking, and an abstraction over which model is called. On AWS you realize it with Bedrock AgentCore Gateway (Module 8) or a home-grown API Gateway + Lambda — what this front door becomes once you add consumer auth and throttling.

Securing the front door

Module 10 secured Relay’s insides — least-privilege IAM bounding what each component can touch. This module secures its outside: who may call the API. Internal IAM (M10) answers “what can the worker Lambda read?”; consumer auth (M11) answers “which partner may POST a ticket?” — a different question, a different layer.

The primary tool for an exposed GenAI API is Amazon Cognito, AWS’s managed identity service for application users. You stand up a user pool, attach a JWT authorizer to the API Gateway route, and every request must carry a valid bearer token — unauthenticated callers never reach a Lambda. (For machine-to-machine, API Gateway also offers API keys and IAM auth.) RBAC — role-based access control — layers on top via token claims checked at the route: a support-agent role may POST /tickets, an admin may also POST /approve. Identity federation trusts an external IdP — CloudCart’s SSO, a partner’s — so users authenticate where they already live, and AWS WAF can sit in front with rate-based and geo rules. In the lab the front door ships open so the round-trip is easy to run.

Infrastructure as code and CI/CD

Every module so far created its AWS resources with imperative boto3 in setup.py. That stops here (decision B6). From M11, Relay’s infrastructure is AWS CDKaws-cdk-lib v2: you describe the stack in Python and CDK synthesizes CloudFormation. An imperative script tells AWS how to build; a declarative stack states what should exist, so CDK diffs, applies only the change, and rolls back to the last good state if a deploy fails. The upstream setup.py stays for the M5–M10 resources the stack references; the API, queue, and bus move to CDK.

On top of the stack sits a CodePipeline — AWS’s managed CI/CD orchestrator — that redeploys Relay on every commit:

Source → Build (uv sync + offline tests + security scan) → Deploy (cdk deploy) → Smoke (curl the live API) → [eval-gate: Module 13]

The Smoke stage is the safety net: it POSTs a real ticket and polls GET against the deployed API, as a client would. If it fails, the pipeline stops before promotion and CloudFormation rolls the failed deploy back — rollback is automatic, so a commit that breaks the API never reaches a healthy “deployed” state. The eval-gate stage — block the deploy on a quality regression — is commented; Module 13 wires it in.

Accessible interfaces (skill 2.5.2) are not a separate build — they fall out of the stack. The API you deployed is an OpenAPI contract: export it with aws apigateway get-export --rest-api-id <id> --stage-name prod --export-type oas30, committed as cdk/openapi.json, and any OpenAPI or AWS Amplify client generates a typed SDK against it. The lab commits that real export — three routes at M11, with POST /feedback arriving in M13 — and the smoke suite fails CI if the deployed API and the committed contract drift. The AIP-C01 exam guide lists 2.5.2 under Task 2.5; here it is a consequence of shipping the API, not a thing to bolt on.

🧠 Theory — skill 2.5.4: Amazon Q Developer in the GenAI dev loop. Amazon Q Developer is an AWS AI coding assistant that targets the developer, not the runtime. In the loop that produced this module it would draft boilerplate (a Lambda handler skeleton, a CDK construct, a boto3 call), refactor an inherited function without changing its contract, generate unit tests against the frozen schemas, and do error-pattern recognition — read a CloudWatch stack trace and propose the fix. It is orthogonal to what Relay ships: Q Developer never enters the request path, never serves a token, and adds nothing to the bill or the IAM surface. It is build-time productivity; on-demand Bedrock plus this serverless stack is the run-time product — conflating the two is the exam’s trap.

Build it: ship Relay behind an API and a pipeline

The lab takes Module 10’s relay/ byte-for-byte and wraps it — adding only the new relay/api/ subpackage, the cdk/ stack, and the pipeline/; the agent is untouched. The full live run cost me $0.10 (as of June 2026), almost all Bedrock agent tokens.

Accept fast, process later (relay/api/post_handler.py, NEW). The fast part — no model call here:

def handle(event, *, sqs_client=None, persist=None, queue_url=None) -> dict:
    try:
        body = common.parse_json_body(event)
        ticket = validate_ticket(body)          # business contract -> clean 400
    except common.BadRequest as err:
        return common.error(400, str(err))
    # Persist `received` FIRST so a GET right after the POST already finds the record,
    # then enqueue. The work is ACCEPTED, not done.
    write_received(ticket, persist=persist)
    enqueue(ticket, body=body, sqs_client=sqs_client, queue_url=queue_url)
    return common.response(202, {"ticket_id": ticket.ticket_id, "status": "received"})

The API paths and bodies are field-for-field from the contract:

POST /tickets                       body {customer_message, channel?} -> 202 {ticket_id}
GET  /tickets/{ticket_id}                                            -> TicketRecord
POST /tickets/{ticket_id}/approve   body {approved: bool}            -> 200 TicketRecord

Run the agent in the background (relay/api/worker_handler.py, NEW). The worker runs the agent through run_relay, then publishes an event by outcome — nothing for a plain answer:

def publish_outcome_event(response, *, events_client=None, bus_name=None) -> str | None:
    status = response.get("status")
    if status == "escalated":
        detail_type = config.RELAY_DETAIL_ESCALATION
    elif status == "awaiting_approval":
        detail_type = config.RELAY_DETAIL_APPROVAL_REQUIRED
    else:
        return None  # answered / failed: no human-routing event to publish.
    client = events_client or _events_client()
    detail = {"ticket_id": response.get("ticket_id"), "status": status,
              "gated": bool(response.get("gated"))}     # id + status only — no PII travels
    client.put_events(Entries=[{
        "Source": config.RELAY_EVENT_SOURCE, "DetailType": detail_type,
        "Detail": json.dumps(detail), "EventBusName": config.resolve_event_bus_name(bus_name)}])
    return detail_type

Because run_relay persists with a PutItem keyed on ticket_id, the worker is idempotent — a redelivered SQS message overwrites the same row — and the event detail carries id and status only, so no PII leaves the bus.

One honest limitation of the deployed worker: it runs tool-light. The Module 7 business tools (lookup_order, create_ticket) reach CloudCart through the MCP server’s Lambda Function URL, but this course account’s SCP blocks public Function URLs, so the CDK does not inject RELAY_MCP_URL into the worker — without it, run_relay degrades to a tool-light run. The doc-answer and refund-approval paths still complete end to end; what a deployed "where is order 1042?" loses is the live order lookup. To restore the tools in your own account, point the worker at the MCP server with a RELAY_MCP_URL env var (a VPC-internal or SigV4-authenticated Function URL, or an API Gateway route) — the agent code does not change.

Describe the stack in CDK (cdk/relay_cdk/api_stack.py, NEW). One file declares the whole front door — the REST API and its request model, the four Lambdas, the SQS queue and DLQ, the relay-events bus, and the escalation rule. Its grants reference upstream resources by their canonical Module 7/10 names — never recreating them — and stay least-privilege, as the worker’s Bedrock grant shows:

worker_fn.add_to_role_policy(iam.PolicyStatement(
    sid="InvokeConverseAndGuardrail",
    actions=["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream",
             "bedrock:Converse", "bedrock:ConverseStream",
             "bedrock:ApplyGuardrail", "bedrock:Retrieve", "bedrock:RetrieveAndGenerate"],
    resources=[f"arn:aws:bedrock:{region}:{account}:inference-profile/*",
               "arn:aws:bedrock:*::foundation-model/*",   # us. profile fans out to members
               f"arn:aws:bedrock:{region}:{account}:guardrail/*",
               f"arn:aws:bedrock:{region}:{account}:knowledge-base/*"]))

The foundation-model ARN spans all regions on purpose: a us. cross-Region inference profile fans the invocation out to its member regions, so scoping it to us-east-1 alone would AccessDenied on the us-east-2 member — the Module 3 rule, now in IAM. The stack also sets API Gateway throttling and a worker reserved-concurrency cap — the auto-scaling lever (4.2.5).

Deploy it and run the round-trip:

$ uv run cdk deploy RelayApiStack            # prints ApiUrl + QueueUrl
$ curl -X POST "${API}tickets" -d @data/tickets/sample.json
-> 202 {"ticket_id": "ticket-577147a9", "status": "received"}
$ curl "${API}tickets/ticket-577147a9"       # immediately
-> {"status": "received", ...}               # the record exists from the first millisecond

The headline increment — a refund ticket — went end-to-end: received → awaiting_approval, with relay.approval_required published to relay-events. The HITL gate then closed over HTTP: POST /tickets/{id}/approve {"approved": true} returned 200, flipping the status to answered and the action’s approved to true; a non-boolean got 400, a re-decided ticket 409, an unknown id 404.

The offline cumulative suite (Modules 2–11) passes 259 passed, 10 skipped, the M11 live round-trip test passed (real POST → poll-GETawaiting_approval), and pipeline/smoke_test_live.py reported SMOKE OK. The skips are inherited MCP-backed agent tests blocked by this account’s policy on public Lambda Function URLs — an environment constraint, not a defect.

Try it yourself. (1) Add an outbound webhook on escalation — an EventBridge rule routing relay.escalation to a small Lambda that POSTs to an external URL; you add a rule and a target, the worker does not change. (2) Add a Cognito user pool and a JWT authorizer on POST /tickets, then call with a bearer token — consumer auth.

In production

A serverless API and a pipeline are things you operate, not ship once.

Cold starts and concurrency. An idle Lambda pays a cold-start penalty on first invocation; provisioned concurrency keeps a pool warm at a price (Module 12). The worker’s reserved-concurrency cap does double duty — it bounds concurrent agent runs, protecting both your Bedrock quota and your bill when the queue spikes.

Failure and idempotence. A ticket that fails repeatedly must not loop forever — the dead-letter queue parks it after a bounded number of receives, so a poison message lands somewhere you can inspect. The worker’s by-key idempotence (above) handles redelivery.

Scale, environments, and quotas. Throttle and meter per consumer with API Gateway usage plans so one partner cannot starve the rest. Promote through dev → staging → prod as separate CDK stacks, with blue/green or canary for risky deploys. A running CodePipeline is the one M11 resource with a real idle cost (~$1/month), so teardown deletes it.

Exam corner

What the exam tests here. Tasks 2.2, 2.3, and 2.5, plus skill 2.4.1 — the deployment and integration core of Domain 2:

  • Deployment strategies (2.2.1): on-demand vs provisioned throughput vs SageMaker AI endpoints, and the idle-cost implication of each.
  • LLM-specific deployment (2.2.2): for a self-served endpoint — container serving + model cold-load (VRAM/KV-cache ceiling), GPU sizing, and cold-start at scale — versus Bedrock, which owns all of it.
  • Sync vs async: API Gateway + SQS + a worker (202 + polling) for long-running calls; request validation.
  • Enterprise integration: EventBridge loose coupling, webhooks, the GenAI gateway pattern.
  • Consumer auth: Cognito, RBAC, identity federation — distinct from internal IAM.
  • CI/CD: CodePipeline build → deploy → smoke → rollback, security scans; Amazon Q Developer (2.5.4) is build-time productivity, orthogonal to the runtime.

Expect at least one numeric deployment trade-off and one matching question pairing a requirement to a service. One data-residency note: skill 2.3.4 (cross-jurisdiction inference — Outposts, Wavelength, edge) is out of scope for this single-Region course, but if a scenario demands inference inside a specific jurisdiction, those are the answers — theory.

Quiz.

  1. A support workload has unpredictable, bursty traffic and a tight budget; most hours see near-zero requests. A second workload needs a guaranteed throughput SLA at high, steady volume. Which serving modes fit?

    • A. Provisioned throughput for both — it’s “production grade.”
    • B. On-demand for the bursty workload; provisioned throughput for the steady high-volume SLA.
    • C. SageMaker endpoints for both, for predictable latency.
    • D. On-demand for both; SLAs don’t matter with serverless.
  2. An agent call takes about 8 seconds, the HTTP client times out at 30, and traffic arrives in bursts. What’s the right API design?

    • A. Increase the client timeout and call the agent synchronously.
    • B. POST /tickets returns 202 and enqueues on SQS; a worker processes it; the client polls GET.
    • C. Run the agent in API Gateway directly with a 60-second timeout.
    • D. Put the agent behind a provisioned-throughput model so it’s faster.
  3. An escalation must reach a human queue today and, soon, also a Slack channel and an analytics store — without Relay knowing about any of them. What pattern fits?

    • A. Have the worker call each system’s API directly in sequence.
    • B. Publish a relay.escalation event to EventBridge; add a rule per consumer.
    • C. Write the escalation to a shared database every system polls.
    • D. Add an if-branch in the worker for each new system.
  4. You’re opening Relay’s API to two partners with different permissions — one may submit tickets, one may also approve refunds — using their own corporate identities. What secures it?

    • A. Tighten the internal IAM roles the Lambdas already use.
    • B. A Cognito authorizer with RBAC claims and identity federation to the partners’ IdPs.
    • C. A NAT gateway and an IP allowlist.
    • D. The Module 9 guardrail on every request.
  5. A deploy ships a broken API to production. Which pipeline design would have caught it before customers did?

    • A. Manual approval before every deploy.
    • B. A smoke stage that curls the deployed API and fails the pipeline before promotion, with automatic rollback.
    • C. More unit tests in the build stage only.
    • D. Deploy on a schedule instead of on commit.

Answers. 1 — B. On-demand fits bursty, budget-sensitive traffic and costs nothing at idle; provisioned throughput is for a guaranteed SLA at steady high volume and is billed idle — the trap is reaching for it by default (A). 2 — B. The async pattern — 202 + SQS + worker + poll-GET — is the answer for long-running calls; raising timeouts (A, C) just moves the wall, and provisioned throughput (D) doesn’t shorten an 8-second loop. 3 — B. A rule per consumer on EventBridge is loose coupling; A and D couple Relay to every downstream system. 4 — B. Consumer auth is Cognito + RBAC + identity federation; internal IAM (A) bounds what the Lambdas touch, not who may call. 5 — B. Post-deploy smoke tests plus automatic rollback stop a broken build; more unit tests (C) miss a deploy-time break.

Traps to avoid.

  • Provisioned throughput is not “the production default.” It’s the guaranteed-throughput, idle-billed option for a real SLA; on-demand is the default, and defaulting to provisioned is a classic cost trap.
  • A self-served endpoint, not Bedrock, owns the cold-start. Skill 2.2.2 is about container model-loading, VRAM/KV-cache limits, and GPU cold-start at scale — what Bedrock on-demand hides.
  • Serverless can stream. API Gateway WebSocket and Lambda response streaming deliver tokens over a serverless stack, on top of the Module 3 ConverseStream. “Serverless can’t stream” is a distractor.
  • Internal IAM ≠ consumer auth. Module 10’s IAM bounds what Relay’s components can touch; Module 11’s Cognito/RBAC bounds who may call the API. The exam mixes them on purpose.

Key takeaways

  • On-demand is the default; provisioned throughput is for guaranteed throughput and is billed idle. SageMaker endpoints serve custom models, billed per instance-hour. Match the mode to the throughput SLA and idle-cost tolerance.
  • POST /tickets returns a ticket_id immediately (202) and runs the agent async behind SQS. A long agent loop must never block the request; the client polls GET.
  • Loose coupling goes through EventBridge, not direct calls. A new consumer is a new rule, not a code change.
  • Securing a GenAI API means authenticating its consumers — Cognito, RBAC, identity federation — distinct from Module 10’s internal IAM.
  • Self-served deployment carries its own surface (skill 2.2.2): container model-loading, VRAM/KV-cache limits, GPU sizing, and cold-start at scale — all of which Bedrock on-demand hides.
  • CDK makes the infrastructure reproducible; the pipeline makes the deploy safe. Declarative stacks diff and roll back; a smoke stage blocks a broken build before promotion.
  • Serverless can stream — API Gateway WebSocket and Lambda response streaming, on top of the Module 3 converse() layer.
  • The only idle-billed M11 resource is the CodePipeline (~$1/month); teardown deletes it.

What’s next

Relay now ships and scales — but every ticket calls a foundation model, and nobody’s counting tokens. Module 12 instruments Relay’s cost per ticket and cuts it with prompt caching, semantic caching, and tiered model routing — turning the cost_cents placeholder that’s sat in TicketRecord since Module 7 into a real number you drive down.

Want the full AIP-C01 question bank and the next module in your inbox? Subscribe here — it’s free, like everything in this series.

Links: Module 11 lab · ← Module 10 · Course index · Module 12 →

References