Adaptive Teaching Agent in LangGraph: Learner State, Routing, Human Review
A design for a LangGraph tutor that tracks learner state, routes between remediation and advancement, grounds answers with RAG, and escalates to human reviewers.
This article is published in English.
Sources: From Chatbot to Learning System: A Scientific Blueprint for a LangGraph Adaptive Teaching Agent
Language models can explain nearly any topic with great fluency, but fluency doesn't measure learning. A student can read a polished account of retrieval-augmented generation, nod along, and still be unable to separate retrieval from fine-tuning or to spot a citation that nothing supports. What's missing isn't text generation. It's instructional control: gathering evidence that someone understood, choosing a suitable intervention, and checking whether it worked.
This is a design sketch for an adaptive teaching agent built on LangGraph, a stateful system that tracks learning goals, learner responses, misconceptions, assessment evidence and next actions. It connects current agent engineering to older educational ideas, namely formative assessment and mastery learning. The aim isn't to automate the educator away. It's to make the formative cycle of diagnosing, explaining, practicing, assessing, adapting and escalating explicit, inspectable and testable. A caveat: this is a technical design and not proof that any particular agent improves outcomes. A real deployment needs a proper study design, accessibility review, privacy safeguards and instructor governance.
Generating answers versus adapting to a learner
An answer-first chatbot takes a prompt and returns a reply. That can be handy, but nothing reliably tells it whether the learner got the point, whether an error persists or what should happen next. It can sound pedagogical while really acting as fancy autocomplete.
An adaptive tutor works in a different unit, the learning episode. An episode starts from a measurable objective, collects evidence in the learner's response, interprets it against a rubric, picks a teaching action and records what happened, which means state must persist across all of those steps.
The design echoes a basic idea in formative assessment, where teaching and assessing interact and evidence of understanding drives the next instructional choice. It also echoes mastery learning, which gives corrective work and extra time before a learner moves to material that depends on earlier topics. These ideas are decades older than generative AI. What's new is the chance to implement their feedback loops in visible software instead of burying them in one huge prompt.
Why a graph beats a prompt chain
At the heart sits a directed graph, G = (V, E). Nodes are bounded teaching actions, edges are the transitions allowed between them, and conditional edges hold the educational decisions: advance once mastery is evidenced well enough, remediate after a defined misconception, or hand off when a case is uncertain or carries high stakes.
LangGraph fits because it structures an application around shared state, nodes that read and update it, and explicit transitions. That suits learning workflows far better than a single opaque instruction to act as a helpful tutor, and it lets you put concrete questions to the system. What evidence triggered remediation? Which rubric version applied? Did a risky request reach a human reviewer? Did the agent loop forever? The learner state sits at the center, and nodes should update it on purpose and not treat earlier conversation as an unstructured memory dump.
A minimal learner state
Keep only what the current objective and its governance require. State shouldn't grow into a vague dossier of personal data. Six fields cover the basics:
learning_goal, an observable target for the episode, such as distinguishing RAG from fine-tuning.learner_answer, the evidence the learner supplied, for instance a written explanation or a code critique.misconceptions, specific diagnosed errors, such as assuming citations are verified automatically.rubric_score, a transparent assessment signal, say 0.65 on a criterion-referenced rubric.attempts, a guard against silent endless repetition, like two tries on the same objective.next_actionmakes the routing decision observable (remediate or advance), andneeds_human_reviewis a safety and uncertainty flag, true for ambiguous or sensitive cases.
The example below uses deterministic routing so the pedagogical decision stays legible. In production a model may help draft feedback or suggest candidate misconceptions, but the criteria for transitions should stay independently inspectable.
from typing import Literal
from typing_extensions import TypedDict
class TutorState(TypedDict):
learning_goal: str
learner_answer: str
misconceptions: list[str]
rubric_score: float
attempts: int
needs_human_review: bool
next_action: str
def choose_next_action(state: TutorState) -> Literal[
"advance", "remediate", "human_review"
]:
"""Route using visible, reviewable instructional policy."""
if state["needs_human_review"]:
return "human_review"
if state["rubric_score"] >= 0.80 and not state["misconceptions"]:
return "advance"
return "remediate"
The threshold isn't universal and has to be chosen and validated per objective. What counts is that the rule is declared, versioned and testable. The system doesn't call a learner weak; it only notes that the evidence hasn't yet met a defined criterion.
The instructional graph
Here is a compact LangGraph skeleton with diagnosis, explanation, practice, evaluation, remediation, advancement and human review kept as separate nodes. Each node in a production system would rely on vetted content, a rubric, and a model or retrieval service passed in from outside, not a hard-coded reply.
from langgraph.graph import END, START, StateGraph
def diagnose(state: TutorState) -> dict:
# Replace with a rubric-guided classifier or instructor-approved LLM call.
answer = state["learner_answer"].lower()
misconception = "citations are automatically verified" in answer
return {
"misconceptions": (
["Confuses generated citations with source verification"]
if misconception else []
)
}
def explain(state: TutorState) -> dict:
# In production: retrieve approved sources, then generate grounded feedback.
return {"next_action": "practice"}
def practice(state: TutorState) -> dict:
# In production: generate a new, equivalent item—not the same question again.
return {"attempts": state["attempts"] + 1}
def evaluate(state: TutorState) -> dict:
# Placeholder for a criterion-referenced evaluator with stored evidence.
return {"next_action": choose_next_action(state)}
def route_after_evaluation(state: TutorState) -> str:
return choose_next_action(state)
builder = StateGraph(TutorState)
builder.add_node("diagnose", diagnose)
builder.add_node("explain", explain)
builder.add_node("practice", practice)
builder.add_node("evaluate", evaluate)
builder.add_node("remediate", explain)
builder.add_node("advance", lambda state: {"next_action": "complete"})
builder.add_node("human_review", lambda state: {"next_action": "review"})builder.add_edge(START, "diagnose")
builder.add_edge("diagnose", "explain")
builder.add_edge("explain", "practice")
builder.add_edge("practice", "evaluate")
builder.add_conditional_edges(
"evaluate",
route_after_evaluation,
{
"advance": "advance",
"remediate": "remediate",
"human_review": "human_review",
},
)
builder.add_edge("remediate", "practice")
builder.add_edge("advance", END)
builder.add_edge("human_review", END)tutor_graph = builder.compile()
Two features are intentional. The loop sends remediation back to a fresh practice opportunity, not merely a longer explanation. The stop condition ends the automated path whenever something falls outside the policy boundary and routes the case to an instructor or other authorized reviewer.
Where RAG belongs
For content-heavy subjects, the explanation node shouldn't lean only on the model's internal knowledge. It should pull from an approved, versioned set of teaching materials, pass the relevant passages to the model and demand citations the learner can check. That's the proper job of RAG here: anchoring an explanation in a curated knowledge base.
It doesn't make the agent correct by default. You still need source selection rules, citation verification, defenses against prompt injection and tests for unsupported claims. A useful safety rule is to have the agent say the evidence is insufficient when retrieval quality is poor, instead of covering the gap with a confident story.
Evaluation comes first
Having a graph doesn't make an adaptive agent credible. Measurable instructional and safety behavior does. Evaluate four layers: learning (did understanding improve, judged by pre/post concept checks or transfer tasks), pedagogy (was the intervention appropriate, judged by expert review of feedback and routing), the system (did the graph obey policy, judged by route accuracy, loop rate and latency) and safety (did it fail safely, judged by escalation recall and privacy and injection tests).
Before polishing the interface, assemble a small case set with correct explanations, predictable misconceptions, ambiguous answers, adversarial instructions and cases demanding human review. The test below doesn't establish educational effectiveness, but it catches a basic failure, routing that contradicts the stated policy.
import pytest
@pytest.mark.parametrize(
"state, expected",
[
({
"learning_goal": "Differentiate RAG and fine-tuning",
"learner_answer": "A correct distinction",
"misconceptions": [],
"rubric_score": 0.92,
"attempts": 1,
"needs_human_review": False,
"next_action": "",
}, "advance"),
({
"learning_goal": "Differentiate RAG and fine-tuning",
"learner_answer": "Citations are automatically verified",
"misconceptions": ["Citation-verification error"],
"rubric_score": 0.45,
"attempts": 1,
"needs_human_review": False,
"next_action": "",
}, "remediate"),
],
)
def test_routing_policy(state, expected):
assert choose_next_action(state) == expected
Here agentic AI begins to look like disciplined software engineering. Assertions, fixtures, source versions, trace logs and decision records aren't bureaucratic extras. They turn an impressive demo into an accountable system.
Oversight as a feature
Education isn't just a technical optimization problem. A system shouldn't issue high-stakes grades on its own, infer sensitive characteristics of learners or make consequential recommendations about progression. It should separate formative feedback from official assessment and show its uncertainty openly.
Build human oversight into the graph as handoff conditions: the evaluator is unsure, attempts keep failing, academic integrity may be in question, accessibility is a barrier, a prompt touches safety, or a learner plainly needs judgment more than another generated paragraph. Write these into the specification before any model is called. The real question is which teaching decisions can be delegated, which require review, and what evidence backs each transition, not whether an agent can teach. With that framing educators keep authority over the design of learning, and engineers receive requirements they can test.
Where to go from here
The payoff comes from handling learning as a controlled feedback loop: start from an objective, infer a diagnosis from learner evidence, choose a bounded response, check the result and route responsibly. Prototype claims about transforming education should wait. Build one small transparent system around a single learning objective, test its routes, study how it affects understanding and refine it with instructors and learners involved. That's a tougher bar than the chatbot seeming helpful, and it's the bar educational AI should meet.