The lab catalog: what you build and what it costs

Every lab in the book, in one place, with its tier and what it proves. Then the sizing math we use before any cloud lab: a small, runnable model of a fleet that tells you the bottleneck, the duration, and the bill before you have spent anything.

The tier system

TierWhere it runsTypical costRules
T0Your laptop, no AWS account$0Pure Python, deterministic where possible (seeded randomness), real output printed in the book
T1AWS free-tier-adjacent servicescentsPay-per-request services only (Lambda, DynamoDB on-demand, SQS, S3); teardown is one script
T2Real deploymentsdollars, stated per labExplicit price estimate up front, budget alarm configured first, tagged resources, teardown checklist verified at the end

Three standing rules for anything above T0, adopted before the first T1 lab and never suspended:

  1. Budget alarm first. An AWS Budgets alert at a small threshold is the first resource every cloud lab creates. Not because the labs are expensive, but because the habit is the lesson.
  2. Everything tagged. Every resource carries a lab: tag; the teardown script deletes by tag, and a final sweep queries by tag to prove the account is clean.
  3. Estimates in writing. Each T2 lab states its expected cost and the worst plausible cost before the deploy command appears. If a lab cannot state those numbers, it is not ready to be a lab.

The catalog

Labs are numbered by part. Titles are stable; details live in the chapters.

#LabTierWhat you build and verify
1.1The 100-line agentT0The loop with three tools against a model API; transcript printer
1.2Schema surgeryT0Bad tool schema vs fixed schema, measured wrong-call rates
1.3The event storeT0Append-only JSONL event log; state as a fold; reused by every later lab
1.4Time travelT0Deterministic replay of a recorded session; inject a fault at turn N, diff runs
2.1Cache ledgerT0Replay a recorded agent session and price it four ways (no cache, 5m, 1h, batch)
2.2Token bucketT0Simulate a 429 storm, then remove it with client-side shaping
2.3First Bedrock callsT1Converse API, inference profile, cache write and read verified in usage fields
3.0MCP from scratchT0A working MCP server and client in 150 lines; the Part 1 loop drives remote, discovered tools
3.1Agent into RuntimeT2Deploy the Part 1 agent into AgentCore Runtime; measure session behavior
3.2Gateway wrapT2An internal REST API becomes MCP tools; called from a Strands agent
4.1Queue contractT0At-least-once delivery made concrete: a redelivery double-fires an effect, an idempotency key makes it exactly-once, a poison message dead-letters
4.2Tool-host LambdaT1One Lambda per tool with per-tool IAM; invoked by the local agent
4.3Thousand-way fan-outT1Distributed map over an embarrassingly parallel job; aggregate in S3
4.4The approval callbackT1waitForTaskToken gate with a CLI approver; timeout and deny paths
5.1Pool schedulerT0Warm-pool simulation: hit rates and start latencies under load
5.2Metal FirecrackerT2Boot microVMs on a bare-metal instance; rootfs build; snapshot restore (follow-along, priced)
6.1The swarm simulatorT0Queue, governor, budget ledger; seeded; the spine of Part 6
6.2Kill and resumeT0Checkpointed fleet run killed mid-flight; resume re-runs only unfinished units
6.3Find-verify meshT0Finder and refuter agents over a synthetic bug corpus; precision-recall table
6.4Fleet on AWST2The simulator's workload on SQS plus Fargate, sharing one real quota
7.1Single-table stateT0The event store on DynamoDB semantics; conditional-write leases raced by two workers
7.2Vectors vs grepT0A vector store from scratch beside a grep tool; where fuzzy recall wins and exact membership wins
7.3Memory eval harnessT0Plant, disturb, probe, score: recall decay as the store fills, and the staleness a ranking cannot retire
8.1Policy scopingT0A tiny IAM engine: a broad role shrunk to a task-scoped session, with the confused-deputy denial shown
8.2Injection drillT0One exfiltration attack against a menu of defenses; detection falls to a rephrase, every structural defense holds
9.1Traced runT0A from-scratch tracer; read one run as a trajectory tree, with the token ledger rolled up from its spans
9.2Cost regression gateT0A CI gate on tokens-per-task: a caching win passes, a prompt-bloat regression fails on the per-task rule the aggregate would miss
11.xCapstone deploymentsT2Each capstone ends in a priced, torn-down deployment with its gate checklist

The T0 spine (1.3, 1.4, 6.1, 6.2) is deliberately load-bearing: those four labs form a miniature Hive on your laptop, and the cloud labs mostly swap one simulated plane at a time for the real service. That is also the debug strategy the book teaches: when the cloud misbehaves, reproduce the shape locally first.

Sizing math: the fleet on the back of an envelope

Before any T2 fleet lab, we size it. The two ideas doing the work here, Little's law and the quota-versus-budget distinction, were introduced with the supermarket analogy in the concepts chapter; this section turns them into arithmetic. Two ceilings govern a fleet's throughput, and whichever is lower is the design.

Ceiling 1: the token quota. If your account allows $Q$ tokens per minute and a work unit consumes $T$ tokens across all its turns, no fleet configuration can exceed

$$\lambda_{\text{tokens}} = \frac{Q}{T} \ \text{units/min}.$$

Ceiling 2: the workers. Little's law relates concurrency, throughput, and time in system: $L = \lambda W$. With $N$ workers each spending $S$ seconds of wall clock per unit,

$$\lambda_{\text{fleet}} = \frac{N}{S / 60} \ \text{units/min}.$$

The run's rate is $\min(\lambda_{\text{tokens}}, \lambda_{\text{fleet}})$, the duration is the unit count divided by that rate, and the right-sized fleet is the $N$ that makes the two ceilings meet:

$$N^{*} = \left\lceil \lambda_{\text{tokens}} \cdot \frac{S}{60} \right\rceil.$$

Workers beyond $N^{*}$ are pure waste: they sit idle behind the governor, or worse, without a governor they turn the excess into 429 errors for everyone sharing the account.

Here is that arithmetic as a runnable calculator, priced with the book's worked-example rates for a Claude Opus 4.8 class model (input $5, output $25 per million tokens; cache reads at 0.1x input, cache writes at 1.25x). Check the live price pages before planning real spend; the shape of the conclusions survives price changes even when the digits do not.

"""Fleet sizing before you spend a dollar: quota math plus Little's law.

Models an overnight fleet run and answers, from a handful of inputs, the
questions that decide the architecture: which resource is the bottleneck
(workers or the token quota), how long the run will take, what it costs with
and without prompt caching, and how many workers the quota can actually feed.

Standard library only. Deterministic: same inputs, same numbers.
"""

import math
from dataclasses import dataclass


# Model-plane price card (dollars per million tokens, Claude Opus 4.8 class).
# Treat these as the book's worked-example snapshot; check the live price
# page before planning real spend.
PRICE_IN = 5.00          # fresh input tokens
PRICE_OUT = 25.00        # output tokens
CACHE_READ_MULT = 0.10   # cache reads bill at 0.1x the input price
CACHE_WRITE_MULT = 1.25  # cache writes bill at 1.25x (5-minute TTL)


@dataclass
class FleetRun:
    units: int               # work units in the run
    tokens_in_per_unit: int  # total input tokens a unit consumes (all turns)
    tokens_out_per_unit: int # total output tokens a unit produces
    cacheable_frac: float    # fraction of input that is a stable shared prefix
    rewrite_frac: float      # fraction of cached volume paying the write premium
    wall_secs_per_unit: int  # wall-clock seconds one worker spends per unit
    workers: int             # workers in the fleet
    tpm_quota: int           # account tokens-per-minute quota (in + out)


def tokens_per_unit(r: FleetRun) -> int:
    return r.tokens_in_per_unit + r.tokens_out_per_unit


def units_per_min_token_cap(r: FleetRun) -> float:
    """Throughput ceiling imposed by the token quota alone."""
    return r.tpm_quota / tokens_per_unit(r)


def units_per_min_worker_cap(r: FleetRun) -> float:
    """Little's law: L = lambda * W  =>  lambda = workers / wall_time."""
    return r.workers / (r.wall_secs_per_unit / 60)


def cost_uncached(r: FleetRun) -> float:
    cin = r.units * r.tokens_in_per_unit / 1e6 * PRICE_IN
    cout = r.units * r.tokens_out_per_unit / 1e6 * PRICE_OUT
    return cin + cout


def cost_cached(r: FleetRun) -> float:
    fresh = r.tokens_in_per_unit * (1 - r.cacheable_frac)
    cached = r.tokens_in_per_unit * r.cacheable_frac
    read_vol = cached * (1 - r.rewrite_frac)
    write_vol = cached * r.rewrite_frac
    per_unit_in = (
        fresh / 1e6 * PRICE_IN
        + read_vol / 1e6 * PRICE_IN * CACHE_READ_MULT
        + write_vol / 1e6 * PRICE_IN * CACHE_WRITE_MULT
    )
    per_unit_out = r.tokens_out_per_unit / 1e6 * PRICE_OUT
    return r.units * (per_unit_in + per_unit_out)


def report(r: FleetRun) -> None:
    tok_cap = units_per_min_token_cap(r)
    wrk_cap = units_per_min_worker_cap(r)
    rate = min(tok_cap, wrk_cap)
    bottleneck = "token quota" if tok_cap < wrk_cap else "workers"
    duration_min = r.units / rate
    right_size = math.ceil(tok_cap * r.wall_secs_per_unit / 60)
    plain = cost_uncached(r)
    cached = cost_cached(r)

    print(f"work units            : {r.units:,}")
    print(f"tokens per unit       : {tokens_per_unit(r):,} "
          f"({r.tokens_in_per_unit:,} in + {r.tokens_out_per_unit:,} out)")
    print(f"throughput cap, tokens: {tok_cap:6.1f} units/min "
          f"(quota {r.tpm_quota:,} TPM)")
    print(f"throughput cap, fleet : {wrk_cap:6.1f} units/min "
          f"({r.workers} workers x {r.wall_secs_per_unit}s/unit)")
    print(f"bottleneck            : {bottleneck}")
    print(f"run duration          : {duration_min:,.0f} min "
          f"({duration_min / 60:.1f} h)")
    print(f"workers the quota can feed: {right_size} "
          f"(the other {r.workers - right_size} would sit idle)")
    print(f"cost, no caching      : ${plain:,.0f}")
    print(f"cost, cached prefix   : ${cached:,.0f} "
          f"({plain / cached:.1f}x cheaper)")
    print(f"cost per work unit    : ${cached / r.units:.3f}")


if __name__ == "__main__":
    overnight_audit = FleetRun(
        units=12_000,            # file-group shards of a large repository
        tokens_in_per_unit=48_000,   # context re-read across ~6 turns
        tokens_out_per_unit=6_000,
        cacheable_frac=0.85,     # system prompt + tools + shared repo context
        rewrite_frac=0.05,       # occasional TTL lapses re-pay the write
        wall_secs_per_unit=240,
        workers=200,
        tpm_quota=2_000_000,
    )
    report(overnight_audit)

Running it for the Capstone A shape (an overnight audit of 12,000 repository shards, 200 workers, a 2M tokens-per-minute quota):

work units            : 12,000
tokens per unit       : 54,000 (48,000 in + 6,000 out)
throughput cap, tokens:   37.0 units/min (quota 2,000,000 TPM)
throughput cap, fleet :   50.0 units/min (200 workers x 240s/unit)
bottleneck            : token quota
run duration          : 324 min (5.4 h)
workers the quota can feed: 149 (the other 51 would sit idle)
cost, no caching      : $4,680
cost, cached prefix   : $2,618 (1.8x cheaper)
cost per work unit    : $0.218

Four design decisions fall straight out of ten lines of arithmetic:

  • The quota is the bottleneck, not the fleet. 200 workers was a guess, and the guess was wrong: the quota feeds 149. The fix is a quota-increase request or a smaller fleet, and it costs nothing to learn this today instead of at 2 a.m. mid-run.
  • Caching helped less than advertised, and the reason matters. 1.8x, not the 10x a naive reading of "cache reads cost 0.1x" suggests. Caching only bends the input curve; the $1,800 of output tokens is untouched, and output is a third of the cached bill here. Fleets whose units write a lot gain less from caching than fleets that read a lot; knowing which kind you have is Part 2's cache-ledger lab.
  • The night is 5.4 hours, so it actually fits in a night. If the same math had said 19 hours, the design conversation changes: batch tier, bigger quota, or fewer, larger shards. Better to have that conversation in front of a calculator.
  • $0.22 per shard is the number to defend. Per-unit cost is the figure that survives contact with finance, scales linearly in your head, and feeds the budget ledger's admission checks in Chapter 2.

Don't be confused: quota vs budget. The quota (tokens per minute) is a rate the provider enforces; the budget (tokens or dollars per run) is a total you enforce. The governor manages the first, the ledger the second, and a healthy fleet is usually pressed against the quota while comfortably inside the budget. Pressed against the budget instead means the work is bigger than the money, and no scheduler can fix that; the admission step should have said no.

The simulator in Part 6 grows this envelope math into a full model: queue dynamics, retries, verification overhead, and checkpoint costs, all seeded and reproducible. But the envelope version already answers the first-order questions, and it is the mental math this book wants you doing reflexively whenever anyone says "we'll just run more agents."

👉 Part 0 ends here, and the build starts: the loop, a hundred lines of Python that the rest of the book never stops using. The references collect the sources behind Part 0's claims.