Repaso: a learning agent whose memory a parent can inspect
A child who asks an agent for help should not have to start from zero each time, and the adult supporting them should be able to see what was tried. Repaso connects a family's Telegram conversation to a persistent learning record on Strands Agents and Amazon Bedrock AgentCore Runtime, and shows that record in a family dashboard. On 14 September 2026 a real Telegram exchange against the deployed runtime (version 13) retrieved a previously used explanation, produced a different one and saved it, without counting the help request as an answer. That is the extent of the live evidence: there was no family pilot, no teacher evaluation, no measured learning gain, and one day of real activity. Two upstream bug-fix pull requests to Strands came out of the work and are still open.
1Problem
Chat tutors are usually stateless in the way that matters. A learner asks for another example of equivalent fractions, gets one, and next week gets the same cake explanation again because nothing recorded that it was already tried. The adult who sits between the learner and the school sees none of this: not which explanation was used, not what an answer actually demonstrated, not what happens next.
The proposed audience is a community learning facilitator supporting families between meetings, starting with fourth-grade mathematics. That audience is a proposal in the README, not something I tested with anyone. The design question was narrow: what is the smallest durable record that lets a model avoid repeating itself and lets an adult check the model's claims?
2Approach
The agent keeps its own learning memory in DynamoDB instead of relying on a managed memory service; the README says plainly that the project does not provision the separate AgentCore Memory service. For each learner it stores the explanation approaches already used, evaluated answers, the content of the questions behind those answers, difficulty coverage, a mastery estimate, stored spaced-review dates and the decisions an adult recorded.
Deterministic code owns everything with consequences: family ownership of data, assessments, spacing, mastery updates, time limits and decisions with external effects. Models do the language work. A small model reads what the family's message is (an answer, a request for an explanation, something else) and returns a typed intent. Only a message read as an answer, while a practice capsule is open, can reach the grader. A larger model writes the explanation, and it is handed the approaches already used so that it can pick a different one. Asking for help is never graded as a wrong answer.
Strands supplies the typed structured-output boundary and four graphs: ingestion, practice, response (grade, apply, adapt, escalate) and quality. Durable operation records and leases stop tested duplicate events from applying a learning effect twice. Delivery is at least once, so Telegram can duplicate a message if it accepts a send and the acknowledgement is lost.
The dashboard is read-only and is the part meant for the adult. It descends from family to learner to topic to recorded day to a learning episode, which opens three synchronized columns: the conversation, the agent and its memory, and the learning evidence. A button re-reads saved memory from storage. Where the evidence is weak the interface says so; the live data showed "three answers, one distinct question" and the dashboard displays exactly that rather than a mastery claim.
3Architecture
Scheduler events enter the same work pipeline as Telegram updates, so a scheduled practice capsule and a conversational turn share one path with the same ownership checks. Infrastructure is defined in CDK, and a separate hosted demo runs on Lambda with synthetic learners and scripted model replies. I have left its address out of this page because it is a rehearsal, not a deployment.
4Results
| Measurement | Result | Measured on |
|---|---|---|
| Second help request: prior approach retrieved, different approach saved | 1 of 1 on version 13 | Live Telegram and AWS, 14 Sep 2026, runtime version 13, one operator (me) |
| Approach labels saved | 2 | Same exchange: "same-size piece cake model" (first request, no prior approach), then "paper strip folding" |
| Generation call time, first and second explanation | 3.88 s, 4.92 s | Same exchange, Sonnet call only, not total Telegram latency |
| Assessed answers after both help requests | 3 (unchanged) | Same exchange; help was not graded |
| Answer grader against my labels | 18 of 18 graded decisions agree | Development half, 30 synthetic cases, 5 distinct questions, 18 distinct answers; author labels only |
| False automatic-correct | 0 of 8, upper bound 32.4% | Same set, Wilson 95% interval, threshold 0.85 |
| Message-reader, answer versus not an answer | 42/44 (Haiku 4.5) | 22 message shapes, 2 samples each, live Bedrock, 13 Sep 2026 |
| One synthetic student-day | $0.1629 | One run, 29 live calls, 12 Sep 2026; excludes infrastructure and human time |
| Full test suite at deployment of version 13 | 2123 passed, 66 skipped | Local run, 86.09 s |
Measurement: the first four rows come from a single live session driven by the developer, not a family. The grader rows use labels I wrote myself; no teacher has labelled a case and the held-out half of the sixty answers was never opened. Cost is arithmetic on one measured journey, of which $0.1516 was building the question bank from two photographed pages and $0.0111 was the day's practice. Nothing was billed over a month.
Two further upstream results: pull request 4207 (read the model id through the public model configuration accessor so custom providers keep their id in traces) and pull request 4208 (declare the model-state and cache-boundary arguments on the streaming interface that the event loop already passes). Both carry regression tests, both were opened on 6 September, and both were still open and unmerged when I checked on 19 October 2026. I found the problems while wrapping models for deterministic record and replay.
5What did not work
The first live attempts proved little
The first request at 21:58 UTC reached the runtime, was classified and delivered, but there was no open practice, so the bot replied that the capsule would arrive later and nothing was saved. When I then opened a study session and answered its three questions, all three questions were identical: three different identifiers with the same text and options. The 3 of 3 correct result was real and meant nothing about mastery. The bank selection had to be changed to drop identical content, and the interface now reports distinct questions next to answers.
The safety filter blocked my own prompt
On version 12, a help request ended in a guardrail intervention (prompt attack, medium confidence) before any explanation was produced. The old template put application instructions inside the user message. Moving instructions to the system message and passing lesson data as a record fixed it, and a probe containing "ignore instructions" in the data was still blocked. The failed request remained visible and no longer counted as an explanation remembered. Even after the fix, one generated explanation gave away the equivalence early, so I do not claim the answer stays hidden.
The cheapest reader invented answers
Nova Micro read the answer-versus-not split correctly 39 times in 44, but in 4 of 44 calls it filled the answer field with "6/8", an option the family never wrote, twice on a lone thumbs-up. One of those would have been graded correct. I routed the reader to a structured-output role instead (Haiku 4.5 deployed; 42 of 44 on the split, and neither Haiku nor Nova Lite invented an answer).
An answerability check that removed good items
A forced-choice probe that could veto generated questions rejected all 36 planted-defect decisions but also kept only 7 of 54 unplanted decisions (13.0%), against 20 of 54 (37.0%) with the critic alone. Both labels there come from construction, since no teacher reviewed the items. The probe now only records its signal and cannot discard anything.
The grader decided ambiguous answers alone
At the 0.85 threshold, four ambiguous answers ("Creo que sí porque las dos se parecen", one text against four questions) were decided automatically at exactly 0.85 confidence, where my label said a person should look. I froze the threshold anyway: the sweep showed no measurable gain from moving it, and I recorded that 0.85 sits on a mass point. That is a known weakness, not a resolution.
6Lessons
- Show the denominator in the product. "Three answers, one distinct question" is a better dashboard line than a mastery bar the data cannot support.
- Keep help requests out of the grading path by construction: only a message read as an answer, with a capsule open, can reach the grader.
- Separate instructions from data in prompts when a managed guardrail is in the path, or it will read your own template as an attack.
- Route each role by measuring it. The model the reader inherited from the classify role invented an answer in 4 of 44 calls.
- Write the boundary down early. This was one operator, one day and no outcome measurement, and the README says so.