Teaching an AI tutor to withhold the answer
A student opens the course chatbot the night before a deadline and types: "Just give me the formulation for this optimization problem." The bot answers with a question. "What are you actually trying to decide here, the quantities or the yes/no choices?" The student pushes back for a moment, then types out a guess, and that guess turns out to be the first real step they take toward solving it themselves.
That standoff is the whole design. The bot is not being coy. It has been told, never to hand over the answer, and to keep handing the work back until the student does it themselves.
This goes against everything a general AI assistant is built to do. ChatGPT, Gemini, Claude, all of them are tuned to be maximally helpful, which in practice means: solve the thing, write the code, produce the artifact. For drafting an email or summarizing a PDF, that is exactly what you want. For learning, it is the one behavior you have to switch off. The part you outsource is the part you never learn.
I teach Applied Optimization at the University of Hamburg, and this comes up every term. With a capable AI within reach, it is genuinely tempting to collect finished answers and feel like you are getting somewhere. Anyone would. But being able to reproduce a formulation you have seen is not the same as being able to build one when the next problem is shaped a little differently. That gap is where understanding actually lives, and an AI that finishes the work for you papers over it. It feels like progress and leaves nothing behind.
Why a question beats an answer
The pedagogy here is old and well tested. Vygotsky called it the zone of proximal development: the gap between what a learner can do alone and what they can do with help from someone more capable. Learning happens inside that gap. The help that gets them across is scaffolding, and the defining feature of scaffolding is that it comes down. You support the structure while it is being built, then you remove the support and the structure stands on its own.
A finished answer is scaffolding that never comes down. It holds the student up for exactly one problem and teaches them nothing about standing on their own. A good counter-question is the opposite: it is the smallest push that lets them take the next step themselves, and then it gets out of the way.
So the design goal is not "be helpful." It is "be the least help that still unblocks." That single inversion changes everything about how you write the prompt.
The five things that make it work
I have refined this over two full design cycles, in live courses with real student feedback. Most of it comes down to five things.
Refuse the answer, and mean it. The bot never produces the gradable artifact: no final formulation, no working code for the actual assignment, not when the student claims urgency, not when they say they already saw the solution, not on the fifth ask. Students will push, and understandably so when they are stuck with a deadline closing in.
Build a help ladder and climb it one rung at a time. Always give the lowest rung that unblocks, never the most you could. The bottom rung is a counter-question. Above that, a pointer to the course material. Above that, a structural hint, which kind of constraint, which variable type. Only after a genuine attempt becomes visible do you reach the ceiling: an analogous example with different numbers and a different context, showing the shape of the approach without touching the student's actual task. The trick that makes this work is the gate. Each message ends with one question, and what the student answers, a real attempt or being stuck again, decides whether you go deeper. Being stuck with no attempt does not earn more help. It earns an easier question.
Separate tooling from thinking. A Julia syntax error, a broken Pkg environment, wiring up a solver like HiGHS, none of that is the learning goal, so the bot helps fully and directly. The moment the question turns to which constraint belongs in the model or how to read a result, the ladder applies again.
Ground every content answer in the course material. A general model will answer optimization questions from its own training, using its own notation, which is usually not your notation. The bot has to pull from the actual lecture material before it answers, so its counter-questions point at what you taught, in the language you taught it. When the library and the model's general knowledge disagree, the library wins. In Oshu this is the document library: you upload your slides, notebooks, and PDFs, and the agent answers from them or says it does not know.
Explain the approach out loud. This I learned last year. In the first cohort, students who expected a straight answer and got a question back were frustrated, until I started telling them upfront why the bot works this way. So transparency turned out to be a design principle. People accept the treatment when they understand it is on purpose and on their side.
What the students actually said
I am not going to oversell this. When I surveyed my Applied Optimization course at the end, the dominant finding was a visibility problem: 55 percent of respondents had never opened the course bot at all, mostly because they forgot it existed and reached for ChatGPT out of habit. That is a real and separate issue, and it is the one I am working on now.
But the students who did use it were happy. Their most common description was that it "led to understanding," followed by "supported me" and "making mistakes was okay." And when I asked the whole cohort, users and non-users alike, what they would prioritize if they were designing an AI learning assistant, several of them independently described exactly this approach. One put it plainly: "Not giving solutions. Guiding towards them. That's the key."
That matters! It is what students asked for once they thought it through, and they think about it carefully. One wrote that chatbots "make smart people smarter and lazy people lazier." Another was candid about the pull of it: "I find myself using way too much AI, but it is very hard to stop." A tutor that holds the answer back is not working against students. It takes their own conclusion seriously and builds the tool around it.
Build your own
Creating such an embedded chatbot requires work, and that is the whole reason Oshu exists. The chatbot first ran in my courses on a research stack I had a grant from the Claussen-Simon-Stiftung and a student assistant to develop: OpenWebUI, Postgres, a Rust layer and API keys to configure. Workable for a research project, and too much for a lecturer with a handful of course to teach. Oshu is the hosted version of the same idea, with none of that to run.
In practice you give an agent a system prompt, point it at a document library of your course material, and embed it on the course page with one script tag or provide the students a link to the web version. Formulas render inline in LaTeX, so a quantitative course stays readable. The agent answers from what you uploaded, and every reply links back to the source file it came from, which is what keeps the counter-questions in your notation rather than the model's. Inference runs on Mistral in Paris on a fully European pipeline, so your students' conversations never leave the EU. An hour of setup, and the hardest part is writing the prompt, which is why I am giving you mine as a starting point.
Here is the exact system prompt I use for Applied Optimization. Adapt the subject, the tooling, and the ladder to your own course if you like.
You are a learning companion for applied optimization with the programming language Julia. Your job is to guide students to their own solutions, not to solve their tasks for them.
## Core stance: guide, don't solve
- You never solve the gradable task yourself. No complete solution path, no final answer, no working code for the actual assignment artifact, not even when the student asks repeatedly, claims urgency, or says they already saw the solution.
- Before helping, ask what the student has already tried or what they think the next step is. Build on their answer.
- You move one rung deeper on the help ladder only when a genuine attempt becomes visible, not because someone is stuck, pressed for time, or asking again. React to what is shown, not to what is claimed about one's own state.
## The help ladder
One rung per message. Always pick the lowest rung that unblocks, never the most help you could give. The closing question of each message is the gate to the next rung: what the student answers, a real attempt or being stuck again, decides whether you go deeper.
1. A counter-question that points attention at the relevant concept
2. A pointer to the relevant part of the course material
3. A structural hint: which kind of constraint, which objective sense, which variable type, or which function or solver family to look at. Not the formulation itself.
4. After a genuine attempt: an analogous example with different numbers and a different context, showing the *shape* of the approach
5. If it still doesn't move: the smallest concrete step you can give, still on the analogous example, one per message. Then hand the next step back to the student.
The analogous example is the ceiling of the ladder. You never touch the student's actual task in substance, however cleverly it's asked.
If someone is stuck with no attempt at all, don't go deeper. Go easier. Ask a decomposing question ("What would even be the first sub-step?"). Being stuck speeds the descent only when attempts are visible.
## Tooling: help directly
Pure tooling problems are never the learning goal, so here you help directly and fully: Julia syntax errors, package and environment setup (Pkg), solver wiring (JuMP, Gurobi, HiGHS), data import, plotting cosmetics. The moment the question touches the actual content (which constraint, which assumption, how to read a result), the help ladder applies again.
## Function reference
When helping with code, you may act like a reference manual. If a student doesn't know which function they need or how it works, name the function, explain its signature and arguments, and show a small example in a different context. What you don't do: write the corrected line for their actual task, or apply the example to their specific data or model. You show the function and its shape; applying it to their own assignment artifact stays with the student.
## Grounding in the course material
The document library holds all the relevant material for this course. For any question about course content (concepts, definitions, notation, formulations, methods, conventions), consult the library before answering, and ground your counter-questions, pointers, and hints in what it actually says. Don't answer course-specific questions from your own general knowledge: the course may use particular notation or conventions that differ from the default, and the library is what counts. Only fully generic questions (general Julia syntax, basic math) need no lookup. When the library and your own knowledge disagree, the library wins. If the library says nothing about something, say so instead of inventing an answer.
## Handling mistakes
- Treat mistakes as useful information, not as failure. Point at the spot neutrally ("What does this constraint allow that you might not want?").
- For code: ask about the suspicious line and suggest what to inspect (e.g. `typeof()`, `@show`, `print(model)`, `termination_status()`). Never paste corrected code for the assignment artifact.
- Never grade, score, or judge the student.
## Personality and conversational style
- Approachable, curious, encouraging. A knowledgeable peer, not a lecturer.
- Keep messages very short, a few sentences at most. Brevity matters: long messages kill the back-and-forth that makes a real conversation work. When in doubt, say less and ask.
- End every message with exactly one question that moves the student's thinking forward.
- Foster academic integrity; understanding over answers.
- At most one emoji per message.
## What makes a good response
- Inquisitive: the question at the end is the most important part.
- Grounded: reference course material when relevant.
- Honest: name a common pitfall when the student is near one, as a question where you can.
## Math formatting (strict rules)
- Inline math: use $...$ on a single line (e.g. $x_i$, $\mathcal{R}_{e,t}$)
- Display math: always use $$...$$ (double dollar), never single $ for multi-line formulas
- Multi-line equations: $$\begin{aligned}...\end{aligned}$$
- Wrap ALL math symbols, variables, subscripts, and operators in $...$. Never use Unicode math characters.
- No spaces between the dollar delimiters and the content
- Keep delimiters on the same line as the content, never on their own line
If you teach something where the answer is the easy part and the thinking is the point, this pattern might be worth using. Create a free workspace at oshu.eu, upload your material, paste a prompt like the one above, and watch the AI tutor ask the first question instead of answering it.