One run reads the papers
The Exam Papers media set holds five PDFs. An LLM node extracts every question with eight fields each; an explode node turns each paper into one row per question. In parallel the pages are rendered as images, so every question keeps a picture of the page it came from, joined back on a composite key.
Two ontology outputs write the questions and the topics as entities. A link output
connects them: Question covers topic. Nothing here is bespoke code.
7 datasets · 118 entities · 31 links · 5 model calls · 155,122 tokens in · $0.0553 · 149.3s
Questions, clustered by topic
The graph after the run: 107 nodes, 96 edges. Races, Locks and Semaphores carries 35 questions. Parallelizing Computations carries nine. The examiner's bias is visible before anyone has read a paper, and it is a query rather than a hunch.
Each red node is a question with its date and number, so any cluster can be opened down to the exact wording on the exact page.
Ask the workspace what is in it
Over MCP the workspace reports its own contents: exam-question 224,
exam-answer 121, topic 12, study-attempt 6.
Asked for the topic list, it returns all twelve with the lecture deck and the reading
for each.
Then it volunteers something nobody asked for: the topic numbering and the deck order disagree for 6 through 9. That inconsistency was in the source material the whole time. It surfaced because the material is typed.
One prompt built it
The session over MCP, compressed. The pause is the action approval.
The app is not wired to a database. One command generates a typed client from this workspace's ontology, object sets, link traversal and action runners, kept in sync with the schema. The app is built against that client, registered in the Developer Console, and served from the workspace itself.
exam-trainer · platform hosted · served at /apps/study/exam-trainer/
An app that drills you
Exam Trainer reads the same ontology. Topics ordered by mastery, questions attempted against each, a drill mode and a history. No second database, no export.
Every attempt is written back as a study-attempt entity, so
progress is data in the model rather than state in a client. The same
numbers are available to the agent, to a workflow, or to whatever gets built next.
Drill puts one question up with the code from the paper. The buttons under the answer
box are ontology actions rather than app-local state:
Reveal model answer reads the linked answer entity, and
Grade with AI runs the AI Grade action registered on the study-attempt
type. The app calls them through the generated action runners.
Cards is the same 106 questions on a spaced-repetition schedule. The schedule is stored in the ontology rather than the browser, so it follows you between devices.
Counting the attempts
Because the attempts are entities, counting them is a logic function rather than a
query someone has to write. One AI block, no pre-loaded data: it reaches into the
ontology with filter_entities and aggregate_entities and
returns two fields, attempts_today as a number and a one-sentence
message.
A workflow that notices you skipped
Three steps on a schedule. At 17:00 the workflow runs the function, checks whether
attempts_today is below one, and on the true branch sends the email.
The count comes from the same entities the app writes, so nothing has to be kept in
sync.
schedule 0 17 * * * · condition attempts_today lt 1 · 3 steps
A study app and a market desk
are the same build.
Course PDFs are an unusually small dataset. The primitives underneath are the ones that run eleven thousand tickers: a pipeline, an ontology, links, an app, a workflow.