Vision & What the Agent Does
Studying for an AWS certification usually means remembering to sit down, open a study app, and grind through flashcards and that habit dies the moment life gets busy. Leitner Loop removes the “open the app” step entirely. It’s an always-on study partner that runs on its own schedule, decides what you need to review next, writes brand-new practice questions for it, and emails them to you no button click required.
Leitner Loop is an automated AWS certification study tool that runs on a scheduled basis, requiring no manual interaction. Every two hours, EventBridge Scheduler triggers a Lambda function that identifies due review topics using a five-box spaced-repetition system, generates fresh multiple-choice questions via Amazon Bedrock, and delivers them by email through SES with clickable answer links.
Clicking an answer routes through API Gateway to a second Lambda that grades the response, provides an explanation, and updates the topic’s review schedule in DynamoDB. Key implementation challenges included defensively parsing Bedrock’s JSON output and adapting to AWS’s deprecation of direct model ID invocation in favor of cross-region inference profiles.
Every two hours, an EventBridge Scheduler rule triggers the agent. It checks a DynamoDB table of exam topics for whichever ones are “due” for review (using a Leitner spaced-repetition schedule), asks Amazon Bedrock (Claude Haiku 4.5) to generate up to 10 fresh, scenario-based multiple-choice questions across those due topics, and emails them to me via SES each answer option is a clickable link.
When I click an answer from my inbox, API Gateway routes it to a second Lambda that grades it, shows the correct answer and explanation, and reschedules that topic further out (if correct) or back to square one (if wrong). It reports back the only way that matters when you’re away from the keyboard: an email waiting for you, and a running record of what you actually know.
The Leitner System
It’s a classic spaced-repetition study technique (invented by Sebastian Leitner in the 1970s using physical flashcard boxes). The core idea: things you know well get reviewed less often, things you get wrong get reviewed again soon.
How it works, generically:
- Cards live in numbered “boxes” (1 = review most often, 5 = review least often).
- Answer correctly ? card moves to the next box up (longer interval before it’s due again).
- Answer incorrectly ? card drops back to box 1 (reviewed again soon).
How agent implements it:
BOX_INTERVALS = {1: 1, 2: 3, 3: 7, 4: 14, 5: 30}
Each exam topic has a box (1–5) and a next_due date stored in DynamoDB. When you answer a question:
-
Correct ?
box = min(box + 1, 5)— moves up a box, next review pushed further out (1 ? 3 ? 7 ? 14 ? 30 days). -
Incorrect ?
box = 1— resets to daily review.
So “Leitner Loop” is the agent’s name because the whole thing is an automated, always-running loop of that Leitner box system: lambda_quiz_generator.py picks topics that are due (next_due <= today) every 2 hours and generates questions for them; answering them via lambda_grading_handler.py feeds the box/interval update back into DynamoDB, which determines what the next run picks.
That closed feedback loop — pick due topics ? quiz ? grade ? reschedule ? repeat is the “loop” in the name.
How I Built It
The core design decision was splitting the agent into two single-purpose Lambdas quiz-generator and grading-handler each with its own IAM role and least-privilege inline policy, rather than one monolithic function. This kept the “generate” path (Bedrock + SES + writes) cleanly separated from the “grade” path (reads + updates triggered by an untrusted public URL click), which also made reasoning about security boundaries much simpler.
The trickiest part was making Bedrock’s output reliable enough to parse automatically every run. Claude occasionally wraps JSON in markdown code fences or adds stray text, so generate_question() strips backticks and a leading json tag before parsing a small defensive step that turned an intermittent failure into a non-issue. I also had to work around AWS deprecating direct invocation of newer Claude model IDs; the fix was switching to a us.-prefixed cross-region inference profile ID instead of a bare model ID.
For the spaced repetition itself, I used a classic 5-box Leitner scheme (`BOX_INTERVALS = {1:1, 2:3, 3:7, 4:14, 5:30}` days) simple, well-understood, and easy to tune. Switching which certification the agent studies is just a one-line change to the EventBridge target’s input payload (`{“cert_code”: “SAA-C03”}`), no redeploy needed, since all cert-specific data (exam domains, topic names) lives in DynamoDB via seed_topics.py rather than in code.
AWS Services Used / Architecture Overview
Services: Amazon EventBridge Scheduler, AWS Lambda (2 functions), Amazon Bedrock (Claude Haiku 4.5 via cross-region inference profile), Amazon SES, Amazon API Gateway (HTTP API), Amazon DynamoDB (2 tables), AWS IAM.
Both DynamoDB tables use on-demand billing and stay within the AWS Free Tier. TTL on `cert-quiz-pending` auto-expires unanswered questions after 48 hours so nothing lingers. The trigger is entirely schedule-driven I never open an app; the agent decides on its own whether there’s anything worth sending.
Cost
Based on current AWS pricing (all us-east-1), assuming the default rate(2 hours) schedule (max 12 runs/day) and up to 10 questions/email:
Total: ? $3–$10/month
Cost driver: Bedrock dominates almost entirely. The range depends on how often topics are actually “due” the Leitner reschedule logic means most 2-hour runs are silent (no due topics), so real-world cost is likely closer to the $1–$3/month range unless you’re actively answering and cycling through many topics daily.
Notes:
- All other services stay within their AWS Free Tier for this workload, so they’re effectively $0.
- First-year SES free tier (3,000 messages/mo) and Lambda’s permanent free tier further reduce costs Lambda’s 1M requests/400K GB-s allowance never expires.
- Sources:
aws.amazon.com/bedrock/pricing,aws.amazon.com/lambda/pricing,aws.amazon.com/eventbridge/pricing,aws.amazon.com/ses/pricing,aws.amazon.com/api-gateway/pricing.
Screenshots
What I Learned
Building this reinforced how much reliability work goes into “just call an LLM” once it’s unattended validating and defensively parsing model output matters far more when there’s no human in the loop to notice a malformed response.
I also learned the hard way that AWS retires direct base-model-ID invocation for newer Bedrock models in favor of cross-region inference profiles, and that IAM policy changes don’t always take effect on warm Lambda execution environments immediately.
On the architecture side, designing around DynamoDB TTL for ephemeral state (pending answers) turned out to be a clean, zero-maintenance way to handle “expire after N hours” without a cleanup job.







답글 남기기