Severity: MEDIUM · Source: docs/backend-contract-bug-audit.md finding #13 · Split out of umbrella #127
Regression introduced when routes moved from legacy <graph_update> XML parsing to Pydantic AI tools.
Problem
end_session derives concepts_covered entirely from the per-message graph_update_json column, but the agent chat path never populates it — so concepts_covered is always [] for agent-path chats.
Verified on main (2026-07-08)
backend/routes/learn.py:581 — _chat_via_agent returns graph_update: {} / mastery_changes: [].backend/routes/learn.py:693 — chat() agent path calls save_message(session_id, "assistant", reply) with no graph_update → graph_update_json stays NULL.backend/routes/learn.py:745-753 — end_session builds concepts_covered from msg["graph_update_json"], so it derives to zero.
Note: a docstring at learn.py:514-518 now rationalizes the empty return as intentional ("frontend reducer accepts empty gracefully") — it only addresses the frontend, not end_session, and must be reconciled with the fix.
Fix
Accumulate what the graph tools merged during the run (e.g. a graph_updates list on SaplingDeps) and pass it to save_message so graph_update_json is populated — or derive concepts_covered from the graph nodes touched during the session.
Status
Fix in flight in PR #243 — adds the graph_updates accumulator on SaplingDeps and persists it. Needs rebase onto current main. PR should Closes this issue.
Umbrella: #127.
Severity: MEDIUM · Source:
docs/backend-contract-bug-audit.mdfinding #13 · Split out of umbrella #127Regression introduced when routes moved from legacy
<graph_update>XML parsing to Pydantic AI tools.Problem
end_sessionderivesconcepts_coveredentirely from the per-messagegraph_update_jsoncolumn, but the agent chat path never populates it — soconcepts_coveredis always[]for agent-path chats.Verified on
main(2026-07-08)backend/routes/learn.py:581—_chat_via_agentreturnsgraph_update: {}/mastery_changes: [].backend/routes/learn.py:693—chat()agent path callssave_message(session_id, "assistant", reply)with nograph_update→graph_update_jsonstays NULL.backend/routes/learn.py:745-753—end_sessionbuildsconcepts_coveredfrommsg["graph_update_json"], so it derives to zero.Note: a docstring at
learn.py:514-518now rationalizes the empty return as intentional ("frontend reducer accepts empty gracefully") — it only addresses the frontend, notend_session, and must be reconciled with the fix.Fix
Accumulate what the graph tools merged during the run (e.g. a
graph_updateslist onSaplingDeps) and pass it tosave_messagesograph_update_jsonis populated — or deriveconcepts_coveredfrom the graph nodes touched during the session.Status
Fix in flight in PR #243 — adds the
graph_updatesaccumulator onSaplingDepsand persists it. Needs rebase onto currentmain. PR shouldClosesthis issue.Umbrella: #127.