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
| Tier | Where it runs | Typical cost | Rules |
|---|---|---|---|
| T0 | Your laptop, no AWS account | $0 | Pure Python, deterministic where possible (seeded randomness), real output printed in the book |
| T1 | AWS free-tier-adjacent services | cents | Pay-per-request services only (Lambda, DynamoDB on-demand, SQS, S3); teardown is one script |
| T2 | Real deployments | dollars, stated per lab | Explicit 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:
- 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.
- 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. - 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.
| # | Lab | Tier | What you build and verify |
|---|---|---|---|
| 1.1 | The 100-line agent | T0 | The loop with three tools against a model API; transcript printer |
| 1.2 | Schema surgery | T0 | Bad tool schema vs fixed schema, measured wrong-call rates |
| 1.3 | The event store | T0 | Append-only JSONL event log; state as a fold; reused by every later lab |
| 1.4 | Time travel | T0 | Deterministic replay of a recorded session; inject a fault at turn N, diff runs |
| 2.1 | Cache ledger | T0 | Replay a recorded agent session and price it four ways (no cache, 5m, 1h, batch) |
| 2.2 | Token bucket | T0 | Simulate a 429 storm, then remove it with client-side shaping |
| 2.3 | First Bedrock calls | T1 | Converse API, inference profile, cache write and read verified in usage fields |
| 3.0 | MCP from scratch | T0 | A working MCP server and client in 150 lines; the Part 1 loop drives remote, discovered tools |
| 3.1 | Agent into Runtime | T2 | Deploy the Part 1 agent into AgentCore Runtime; measure session behavior |
| 3.2 | Gateway wrap | T2 | An internal REST API becomes MCP tools; called from a Strands agent |
| 4.1 | Queue contract | T0 | At-least-once delivery made concrete: a redelivery double-fires an effect, an idempotency key makes it exactly-once, a poison message dead-letters |
| 4.2 | Tool-host Lambda | T1 | One Lambda per tool with per-tool IAM; invoked by the local agent |
| 4.3 | Thousand-way fan-out | T1 | Distributed map over an embarrassingly parallel job; aggregate in S3 |
| 4.4 | The approval callback | T1 | waitForTaskToken gate with a CLI approver; timeout and deny paths |
| 5.1 | Pool scheduler | T0 | Warm-pool simulation: hit rates and start latencies under load |
| 5.2 | Metal Firecracker | T2 | Boot microVMs on a bare-metal instance; rootfs build; snapshot restore (follow-along, priced) |
| 6.1 | The swarm simulator | T0 | Queue, governor, budget ledger; seeded; the spine of Part 6 |
| 6.2 | Kill and resume | T0 | Checkpointed fleet run killed mid-flight; resume re-runs only unfinished units |
| 6.3 | Find-verify mesh | T0 | Finder and refuter agents over a synthetic bug corpus; precision-recall table |
| 6.4 | Fleet on AWS | T2 | The simulator's workload on SQS plus Fargate, sharing one real quota |
| 7.1 | Single-table state | T0 | The event store on DynamoDB semantics; conditional-write leases raced by two workers |
| 7.2 | Vectors vs grep | T0 | A vector store from scratch beside a grep tool; where fuzzy recall wins and exact membership wins |
| 7.3 | Memory eval harness | T0 | Plant, disturb, probe, score: recall decay as the store fills, and the staleness a ranking cannot retire |
| 8.1 | Policy scoping | T0 | A tiny IAM engine: a broad role shrunk to a task-scoped session, with the confused-deputy denial shown |
| 8.2 | Injection drill | T0 | One exfiltration attack against a menu of defenses; detection falls to a rephrase, every structural defense holds |
| 9.1 | Traced run | T0 | A from-scratch tracer; read one run as a trajectory tree, with the token ledger rolled up from its spans |
| 9.2 | Cost regression gate | T0 | A 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.x | Capstone deployments | T2 | Each 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.