I run three platforms on the same infrastructure. The first runs autonomous data operations on a schedule. The second is a financial intelligence system that publishes a daily brief. Both call gamma, the service I described in the first post in this series: an HTTP endpoint in front of a coding agent. Send a role name and a prompt, get that role's answer back.
The second post covered what changes when the agent's output is prose that readers see. This one is about the third platform, and it introduced a constraint the other two never had.
Verso is a learning platform for my two kids. My son is eleven and in sixth grade, my daughter is seven and in second. That is the entire user base, and it broke an assumption I did not know I was making: somebody is waiting.
Nobody was waiting on the first two
The orchestration platform runs at four in the morning. If a call takes ninety seconds, nothing notices. The publishing platform has a reader, but the reader is me, on my own schedule.
Verso has a second-grader holding a pencil, watching the screen, waiting for the next question. Her patience is a fixed limit, not something I get to negotiate. When I exceed it she leaves, and the product has failed in the only way that matters.
Same endpoint, same infrastructure, different pressure. It produced a design mistake I did not see until I watched someone hit it.
Fourteen roles
Verso teaches eight subjects. Reading and writing is the flagship: read a chapter, write about it by hand, photograph the page, get it critiqued. The others are pattern recognition, math, money, negotiation, psychology, strategy, and a video vein.
Fourteen roles are registered with gamma. Seven stay warm because they sit on the child's critical path. The other seven boot on first use and pay a fifteen to thirty second warm-up that a loading screen covers.
The writing coach runs the flagship loop in one call: read the photograph of the handwriting, grade the mechanics, check what the child wrote against the actual text of the chapter, estimate reading and writing level, and author the follow-up drills.
Three generators, each with its own grader. One for pattern recognition, one for math, one shared across the four conceptual subjects. Setting work and marking it require different dispositions. The generator composed the question and knows what it intended. The checker is looking at a photograph of a seven-year-old's pencil work and has to be generous about the handwriting and strict about the idea: accept a smudged, lopsided, correct answer, reject a neat wrong one. A single role doing both grades its own question, and grades it favourably.
One role, four subjects. The conceptual generator serves money, negotiation, psychology and strategy from one definition. A tag in the prompt selects the strands and the grade ladder. Adding a fifth is a line of config, not a new agent.
The occasional roles. Book-page OCR, the one-time baseline assessment, the revise-and-resubmit check, the video-lesson builder and its grader, and a drawing animator that turns a child's sketch into a rigged SVG character. Bursty or once-per-child, so they do not earn a warm session.
One role writes for me rather than for them: it reads a child's drawing and returns a developmental assessment covering stage, fine motor control, use of detail, and what to encourage next. Getting the audience wrong there does the most damage, because the register that suits a seven-year-old is condescending to a parent, and the register that suits a parent is unreadable to the child.
The obvious design was wrong
For the leveled subjects I first generated content one question at a time. Each call is small, so it is fast and unlikely to come back malformed. I prefetched the next question while the child worked on the current one.
A child does not fill that gap the way a scheduler does. They answer, then they stop. If the prefetch had not landed they watched a spinner, and they did it between every question. Six items in a set, six chances to lose them. The design distributed waiting across exactly the moments when the child had nothing to do.
The instinct is to make the calls faster. That is wrong in a way that generalises: you cannot make the agent fast enough to be invisible, so change when it runs instead.
Moving the waiting to the front
Verso now works in mini curricula. When a child starts a subject, gamma plans three lessons at once from their last results, then builds each one complete in a single call: every question, every figure, every answer, stored before the child sees any of it.
Lesson one takes a minute or two. They wait for that once, then lessons two and three build in the background while they work. After that, opening a lesson is a database read, measured at six milliseconds.
The waiting did not shrink. It moved to the front, where a child tolerates it because nothing has started yet, in exchange for no waiting during the part where they are engaged. Two details make it hold up.
The lesson has to be genuinely complete, not a plan with placeholders. If any item needs a round trip when the child reaches it, the scheme collapses back into the original problem. Across all stored lessons, zero items require generation mid-lesson.
The background build has to survive the server restarting. Those builds are in-flight async tasks, and a restart kills them silently, leaving a lesson stuck at "building" forever. So the endpoint that reads curriculum state also re-arms any stalled lesson. Without it the feature would break quietly on every deploy.
When all three are done, gamma reads the per-skill results, not just scores but which specific skills were hit and missed, writes a short analysis, and plans the next three around it. On one run where the written-work item had been missed every time, it reported exactly that and built the next lesson around showing the steps. I did not write that rule. There is no rule.
The output is an artifact, not an answer
The orchestration platform asks the agent for decisions: which category, did this succeed, is this worth retrying. The publishing platform asks for sentences. Verso asks for artifacts: working software a child interacts with directly.
Across the sessions on disk there are 280 generated items. 143 are multiple choice, 44 carry a diagram the agent drew as SVG, and 22 are applets: self-contained interactive mini-apps in HTML and JavaScript, loaded into a sandboxed iframe. Drag the marker to three-quarters. Shade the bottom bar to match the top. Add ten-frames until you make the number. Roughly 72,000 characters of working code, written to spec, for two children.
I did not build a widget library for the agent to fill in. It writes the app. The contract is a strict output shape and a rule that it reports its score to the parent page exactly once.
That creates a new failure mode: the agent can ship broken code. A stray newline inside a JavaScript string and the iframe is a blank rectangle. On the publishing platform a malformed response sits in a queue until Tuesday. Here a seven-year-old is staring at a blank box with no idea what she did wrong.
So every applet gets an error guard injected. A failure swaps the broken item for a card that says woops, I'm not perfect yet, let's try that one again together, with a button that re-prompts gamma for that one item and a quiet "skip this one" underneath. That is the only per-item call left in the system, and it exists so a child is never stranded. A dead end is worse than a wrong answer, because a wrong answer is still the lesson working.
It reads the book and the handwriting
I built the book-page OCR on Tesseract first, locally, properly tuned: downscaling, adaptive thresholding, per-word confidence filtering. On real photographs of a paperback taken by a kid it produced gibberish. Curved pages, low contrast, a thumb in the frame. I tuned it for a while, then removed it from the primary path. Gamma reads the same pages correctly in about nine seconds. Tesseract remains as a fallback for when gamma is unreachable. Fourteen chapters and 7,817 words of book text are indexed that way.
The same capability does something I could not have built at all: it reads the children's handwriting. Sixteen writing sessions have gone through it. The child reads a chapter, writes about it on paper, photographs it, and the agent reads the handwriting, grades the grammar, and checks what they wrote against the actual text of the chapter they claim to have read.
That last check is the anti-gaming mechanism, and it works only because the agent has both the child's sentences and the source. It is not comparing strings. It is judging whether this summary belongs to that chapter. I do not know how to write that function.
The reader cannot read
The instruction "Draw the next two steps of the pattern, then take a photo" is itself a reading comprehension test my seven-year-old did not sign up for. For a while I was the workaround: she would get stuck, and I would read the screen to her. The app worked correctly and was unusable without an adult beside her.
Every instruction now has a read-aloud button, and the speech runs through a local voice service. That is not a feature I would have prioritised from a spec. It came from watching one child fail at one screen.
It also puts two constraints on the role definitions. Every child-facing string has to sit at or below that specific child's reading level, which the platform tracks per subject and which differs between my two kids by four grades. And every child-facing string is forbidden from using em dashes and en dashes, because those strings go to a speech synthesiser and a dash produces a swallowed pause in the middle of a sentence a child is trying to follow. In Verso a dash is a defect, and the role definition has to say so explicitly or the agent writes prose that reads well and sounds wrong.
The bug that was not a bug
The most instructive failure was not a broken applet or a bad grade. I counted where the correct answer sat in every multiple-choice item the agents had produced. It was in the first position 31 times out of 42. Never once in the fourth.
Nothing was broken. Every question was pedagogically sound, correctly answered, well explained. The agent has a positional habit: the right answer tends to come out first.
My son is eleven, and eleven-year-olds are good at noticing exactly this. A child who works out that the top choice is usually right has learned something real, and it is not fractions. He would score well, the platform would raise his level, and it would be measuring how well he predicts my quiz format instead of measuring the subject.
I did tell the roles to vary the position. I do not trust that, because asking a model to self-randomise is asking it to do the one thing it is structurally bad at. The fix is about thirty lines of Python that shuffle the options and rewrite the answer index once, when the lesson is stored. Uniform across positions, verified over four thousand runs.
The lesson generalises past this app. The agent is not always wrong in ways it can be instructed out of. Sometimes it is competent, obedient, and biased. The right response is not a firmer prompt. It is ten lines of deterministic code between the agent and the user, doing the one narrow thing the agent will never reliably do itself.
What the third platform taught me
The first platform proved an agent could be called from code. The second proved it could be given a byline. The third put an agent in front of a child, and what showed up was neither a correctness problem nor an editorial one.
It was patience and trust. A child will not wait, and will not come back to something that has embarrassed them. That turns latency into a design constraint rather than a performance metric, one you relocate rather than optimise, and it turns every failure into something that needs a friendly exit rather than a queue.
Two users. Twelve curricula planned, thirty-six lessons built, 280 items, 72,000 characters of generated software, and two chapter books read by machine vision. Small numbers next to the other platforms.
Still enough to teach me what I would have missed at scale, because you only learn it by sitting next to the user: my daughter did not need the agent to be smarter. She needed it ready when she was, and kind when it broke.
This is the engine behind our builds. The architecture described here is what runs every OpenRed client automation — see the services page for what it does in plain terms, or book a free 30-minute call.
More from the OpenRed blog
- What can AI automation actually do for a small business?
- How much does AI automation cost for a small business?
- AI automation consultant vs. hiring a developer vs. DIY tools
- Why the most useful question is almost always the backwards one
- The reviewer was checking the paperwork, not the work
- The model half improves on its own. The other half is yours
- Your agents have amnesia, and it is costing you
- The real invention was turning a coding agent into a webservice
- I don't call the agent for decisions. I call it for sentences