« Phase 01 · Warmup · Track Overview
Lab 01 — The Agent Kernel
The problem
Thirty teams want to run agents on your platform. Each of them will, if you let them, write their own loop — and each of those loops will forget the step budget, keep state in process memory, grow the context until the run dies, and log nothing an auditor can use.
Your job is to make that unnecessary. You build the kernel: the runtime that owns the lifecycle, enforces the budgets, externalizes the state, manages the memory tiers, and emits the execution chain. Agent authors bring a goal, a tool set, and a policy. Everything else is yours.
The OS analogy is exact and worth holding onto: a process does not choose its own memory limit, is not trusted to yield the CPU voluntarily, and cannot decide where its page tables live. Neither should an agent.
What you build
| # | Component | What it does |
|---|---|---|
| 1 | RunState, Event, TRANSITIONS, transition | an explicit lifecycle state machine; anything not in the table is an IllegalTransition, and terminal states accept nothing |
| 2 | Step, Scratchpad | working memory with token accounting and compaction that never eats the recent window |
| 3 | Fact, SemanticMemory | long-term facts partitioned by (scope, owner); visibility is filtered before ranking, never after |
| 4 | Episode, EpisodicMemory | memory of what happened, recalled by tag overlap then recency |
| 5 | SessionSnapshot, SessionStore | externalized state with optimistic concurrency — a stale write is rejected, not merged |
| 6 | AffinityRouter | consistent-hash session routing with virtual nodes, drain(), and a stable hash so restarts do not reshuffle |
| 7 | Budgets, BudgetBreach | kernel-enforced steps, tokens, cost, and deadline |
| 8 | Decision, Policy | the injected model, as a pure function of the rendered scratchpad |
| 9 | AgentKernel | the loop: budget-check → decide → dispatch → observe → compact → checkpoint, with HITL pause/resume and an execution_chain read from the store |
Key concepts
| Concept | Where | Why it matters |
|---|---|---|
| Explicit transition table | TRANSITIONS | an undeclared transition is a bug you can prove does not exist |
| Recoverable vs fatal error | run() unknown-tool branch | a hallucinated tool name feeds back as an observation; a budget breach ends the run |
| Budget-before-call | run() loop head | checking after the model call means paying for the step you are about to reject |
| Compaction | Scratchpad.compact_if_needed | attacks the quadratic scratchpad term; lossy for the model, never for the audit record |
| Scope-partitioned memory | SemanticMemory.search | the same defect class as an un-namespaced vector index |
| Optimistic concurrency | SessionStore.save | two workers, one session, exactly one winner |
| Externalized state | SessionSnapshot | makes affinity an optimization instead of a requirement |
| Consistent hashing | AffinityRouter | adding a replica moves ~1/n of sessions; modulo moves ~(n−1)/n |
| Stable hash | _hash_to_int | hash() is salted per process — a ring built on it reshuffles every restart |
| Draining | AffinityRouter.drain | rolling deploys need "no new work here", not "gone" |
| Execution chain | execution_chain() | the debugging artifact and the audit artifact are the same object |
Files
| File | Role |
|---|---|
| lab.py | your implementation |
| solution.py | reference; python solution.py prints eight worked sections |
| test_lab.py | 60 tests |
| requirements.txt | pytest |
Run
pip install -r requirements.txt
pytest test_lab.py -v
LAB_MODULE=solution pytest test_lab.py -v
python solution.py
Success criteria
-
All 60 tests green against your
lab.py. - Every terminal state rejects every event — the test iterates all of them.
- No state is a trap: every non-terminal state has an edge to a terminal state.
-
compact_if_neededreturnsFalsewhen under budget and when the only steps left are the recent window. -
SemanticMemory.searchreturns[]for a caller holding no scope, whatever the tags. -
A stale
saveraisesConcurrentModification— it does not silently overwrite. -
Two
AffinityRouters built with different insertion order route identically. - Adding a replica to a 3-node ring moves between 10% and 45% of sessions, and every moved session goes to the new replica (no churn between existing ones).
-
The policy is called exactly
max_stepstimes when an agent loops — notmax_steps + 1. -
execution_chainstill has every step after a compaction.
How this maps to the real stack
| This lab | The real thing | What we simplified |
|---|---|---|
TRANSITIONS | LangGraph's compiled graph and its interrupt states; Bedrock AgentCore session states; ADK's event-driven runner | real runtimes fold the state machine into a graph executor, so the states are implicit in the graph topology — which is exactly why an explicit table is worth building once |
Scratchpad + compaction | LangGraph message trimming / summarization nodes, Claude Code's context compaction, LlamaIndex chat memory buffers | real compaction calls a model and is tuned against a quality eval; ours is deterministic so the test can assert it |
SemanticMemory scopes | ADK's user:/app:/temp: state prefixes; AgentCore Memory's short/long-term strategies; mem0-style stores | real stores add embedding search and TTLs; the scope partition is the part that must not change |
EpisodicMemory | agent "experience" stores and trajectory replays used for few-shot selection | real recall uses embeddings, not tag overlap |
SessionStore + CAS | a Postgres/DynamoDB session row with a version column, or a LangGraph checkpointer (MemorySaver, PostgresSaver) | real checkpointers also store the graph's pending tasks so resumption is exact mid-node |
AffinityRouter | Envoy/Istio consistent-hash load balancing (ring_hash, maglev) on a session header; Kubernetes sessionAffinity | real rings run thousands of virtual nodes and are weighted by replica capacity |
Budgets | LangGraph recursion_limit, provider max-tokens, gateway cost ceilings, request deadlines | real budgets are enforced in several places at once (kernel, gateway, mesh timeout) |
execution_chain | OpenTelemetry spans with GenAI semantic conventions, exported to Tempo/Jaeger/Datadog | real chains are spans in a distributed trace, not a list in a row |
Honest limits. The kernel is single-process and synchronous. It does not do parallel tool calls, streaming, multi-agent fan-out, or true durable execution across a crash mid-tool-call (the checkpoint happens after the observation, so a crash during dispatch replays the call — which is precisely why the action gateway's idempotency keys in Phase 10 are not optional).
Extensions
- Crash-safe dispatch. Checkpoint before the tool call with the call recorded as
in_flight, and on resume either re-dispatch with the same idempotency key or reconcile. That is the difference between "checkpointed" and "durable." - Parallel tool calls. Let a
Decisioncarry several tool calls, dispatch them concurrently, and merge observations deterministically (sort by tool name) so the trace stays diffable. - Streaming. Yield partial steps from
run()as a generator and assert the stream is a prefix-consistent view of the final result. - Capacity-weighted ring. Give each replica a weight and allocate virtual nodes proportionally; verify the distribution matches the weights.
- Memory promotion. Add a rule that promotes a fact observed in three episodes into
SemanticMemory, with provenance — then think hard about what that means for the audit trail. - A second planner. Implement ReWOO (plan the whole chain up front, execute, then solve) as
an alternative
run()mode, and compare step counts and token totals on the same policy.
Interview / resume bullets
- "Built the platform's agent kernel: an explicit lifecycle state machine with a provable transition table, kernel-enforced step/token/cost/deadline budgets, externalized session state with optimistic concurrency, and an execution chain that doubles as the audit artifact."
- "Made session affinity an optimization rather than a requirement by externalizing state, then implemented consistent-hash routing with draining so a rolling deploy moves ~1/n of sessions instead of all of them."
- "Cut long-run token cost by adding scratchpad compaction that folds old steps into a summary while preserving the recent window — attacking the quadratic growth term without losing the full record, which stays in the checkpointed chain."
- "Classified agent failures into recoverable (a hallucinated tool name feeds back as an observation) and fatal (a budget breach ends the run), which turned a class of agent bugs into self-correction instead of incidents."