Implementation Plan — Agent Document Editor + LLM Consolidation into the Chatbot Microservice¶
Status: plan only — do NOT implement yet (per direction 2026-06-21). Feature doc:
docs/features/agent-document-editor.mdRelated:docs/backend_microservice_extraction_plan.md(monolith slimming — dovetails with Phase 0 here);docs/engineering/chatbot-agent-platform-migration.md(the full cards→agents picture; this plan is its editor/WS2 slice). Last updated: 2026-06-24 — addedassignmentdoc type + Publish-to-Assignment, full visual block kinds, assignment as the Phase 1 pilot.
0. Two goals, kept separate¶
This plan covers two intertwined but distinct workstreams. Keep them separate so they can ship independently:
- LLM consolidation — move all LLM-calling generation into the chatbot microservice. The FastAPI backend becomes a thin proxy + persistence layer with no generation prompts.
- Agent Document Editor — give lesson_plan / question_bank / lesson-module outputs an editable, exportable two-pane editor (
/documents/:id), reusing the existing TipTapNotionEditor.
Workstream 1 is the precondition for the refine ("regenerate this block") feature in workstream 2, but the editor's read/edit/export can ship before refine.
1. Current state (verified)¶
| Concern | Today | Source |
|---|---|---|
lesson_plan / question_bank chat modes |
Already proxied to external chatbot service | apps/backend/src/services/chatbot_client.py:180 (stream_chat_sse); routed from ai_tutor.py:1041 _stream_chat_via_chatbot |
ai_teacher generation (lesson/homework/quiz) |
LLM called locally in FastAPI via prompts | apps/backend/src/core/ai/prompts.py (LESSON_GENERATION_PROMPT:109, HOMEWORK_GENERATION_PROMPT:178, KNOWLEDGE_CHECK_GENERATION_PROMPT:979); consumed by api/v1/ai_teacher.py:528,670 |
| Output of lesson_plan/question_bank | Plain streamed markdown into messages.content |
MessageBubble.tsx (no card; renders markdown) |
| Editable document store | None — only conversations/messages |
models/communication.py:62,88 |
| Editor infra | TipTap NotionEditor + AIPanel already exist |
apps/web/src/components/content-editor/ |
| Dedicated artifact route precedent | /presentations/:id + Presentation table |
PresentationViewerPage, models/presentation.py:43 |
| Export precedent | doc-intelligence service does HTML→PDF/PPTX; research does DOCX/MD | services/presentation.py:908, api/v1/deep_research.py |
Key consequence: the consolidation is ~half done. lesson_plan/question_bank already live in the microservice; the ai_teacher path and all the prompts in prompts.py do not.
2. Target architecture¶
┌────────────┐ SSE/REST ┌──────────────────────────┐
│ apps/web │ ──────────────▶ │ FastAPI backend │
│ (editor + │ │ (proxy + persistence) │
│ chat) │ ◀────────────── │ - documents CRUD │
└────────────┘ │ - export proxy │
│ - auth, quota, RAG ctx │
└──────────┬───────────────┘
│ gRPC/REST + SSE
▼
┌──────────────────────────┐
│ Chatbot microservice │
│ OWNS ALL LLM GENERATION │
│ - chat (all modes) │
│ - lesson_plan / qbank / │
│ module generation │
│ - block-level refine │
│ - ALL prompts live here │
└──────────────────────────┘
Boundary rules:
- FastAPI holds zero generation prompts after Phase 0. prompts.py generation strings move to the microservice.
- FastAPI still owns: auth, quota/entitlements, persistence (documents, conversations), RAG context assembly (it can pass curriculum context to the microservice, but does not call the LLM).
- Export stays a service call (doc-intelligence), proxied by FastAPI — not in the chatbot service.
3. The document contract (microservice ⇄ backend ⇄ web)¶
For block-level refine to work, generation must return structured, addressable output, not an opaque markdown blob. Define a versioned contract owned by the microservice:
// document generation result (v1)
{
"doc_type": "lesson_plan" | "question_bank" | "module",
"title": "Introduction to Right-Triangle Trigonometry",
"schema_version": 1,
"blocks": [
{ "id": "b1", "kind": "heading", "level": 1, "text": "Learning Objective" },
{ "id": "b2", "kind": "paragraph", "text": "Students will be able to ..." },
{ "id": "q1", "kind": "mcq", "stem": "...", "options": ["A","B","C","D"],
"answer_index": 2, "explanation": "...", "difficulty": "medium", "bloom": "apply" },
// visual block kinds (locked full scope — agents return text + visuals):
{ "id": "m1", "kind": "math", "latex": "\\cos 30^\\circ = \\frac{10}{hyp}" },
{ "id": "c1", "kind": "chart", "chart_type": "bar", "data": [/* {label,value} */] },
{ "id": "t1", "kind": "table", "headers": ["A","B"], "rows": [["1","2"]] },
{ "id": "i1", "kind": "image", "prompt": "right triangle labelled ...", "url": "gs://..." },
{ "id": "d1", "kind": "diagram", "engine": "mermaid", "source": "graph TD; A-->B" }
// ...
],
"meta": { "grade_level": "...", "subject": "...", "audience": "higher_ed", "language": "en" }
}
doc_typeincludesassignment(structured/gradeable — see §4.4) in addition tolesson_plan/question_bank/module.-
Visual block kinds map to existing TipTap nodes:
math→MathBlock,chart→ChartBlock(Recharts),table→TipTap table,image→ImageBlock(GCS),diagram→newMermaidBlock(net-new; reuse the chatMermaidDiagramrenderer + DOMPurify SVG sanitize).imageblocks are produced by a chatbot agent image tool (relocated Imagen/Gemini/DALL·E), not the old in-processai_teachermedia path. -
Web maps
blocks[]→ TipTap document JSON (and back) deterministically. Each block keeps a stableidso refine can target one block. - Refine request:
{ doc_id, block_id, instruction: "make harder" | "regenerate" | "shorten" | free-text }→ microservice returns the replacement block(s) with the sameid. - Streaming generation still streams text for the chat card; the structured payload arrives in the
doneevent (mirrors howgeneratePresentationStreamreturnsTPresentationDonePayload).
Decision needed (open): keep KaTeX as
$...$insidetext/stemfields (web already renders KaTeX) — simplest. Documented in feature doc open questions.
4. Backend (FastAPI) changes — proxy + persistence only¶
4.1 New documents table (Alembic migration)¶
Model apps/backend/src/models/document.py (mirrors Presentation + content_blocks patterns):
| Column | Type | Notes |
|---|---|---|
id |
UUID PK | |
owner_id |
FK users | |
conversation_id |
FK conversations, nullable | originating chat |
doc_type |
enum(lesson_plan,question_bank,assignment,module) |
assignment publishes to the gradeable assignments table (§4.4) |
published_assignment_id |
FK assignments, nullable | set on Publish to Assignment (assignment docs only) |
title |
str | |
body_json |
JSON | TipTap doc JSON (source of truth for editing) |
body_html |
Text | rendered HTML (for export + preview) |
source_blocks |
JSON | the contract blocks[] (enables block-refine + re-derive) |
meta |
JSON | grade/subject/audience/language |
status |
enum(draft,ready) |
|
created_at / updated_at |
ts | last-write-wins autosave |
Defer a document_versions table to Phase 2 (history).
4.2 Endpoints (apps/backend/src/api/v1/documents.py)¶
| Method | Path | Purpose |
|---|---|---|
| POST | /documents |
create from generation done payload (stores contract + initial TipTap json/html) |
| GET | /documents |
list (owner-scoped; filter by doc_type) |
| GET | /documents/{id} |
fetch for editor |
| PATCH | /documents/{id} |
autosave body_json/body_html/title |
| POST | /documents/{id}/refine |
proxy block-refine to microservice; persist returned block |
| GET | /documents/{id}/export?format=docx\|pdf\|markdown |
proxy to doc-intelligence export (reuse services/presentation.py export path generalized to arbitrary HTML) |
| POST | /documents/{id}/publish-assignment |
assignment only — map question blocks → Assignment.questions + settings/targets; create the gradeable assignment via existing homework.py create path |
| DELETE | /documents/{id} |
soft delete |
/refineand generation calls go throughchatbot_client(extend it withgenerate_document+refine_blockmethods). No prompts in FastAPI.- Quota/entitlement checks stay here (lesson_plan/question_bank are Pro-gated per
chat-mode-surface.md; assignment gen usesAIFeatureType.CONTENT_GENERATION, the existinggenerate-homeworkgate).
4.4 Assignment document type (structured → gradeable)¶
assignment reuses the editor + contract but is not a free document. The card flow:
enter details (topic / subject / type / count / difficulty / due date / targets) → agent
generates (POST /v1/agents/assignment, replaces in-process /ai-teacher/generate-homework)
→ opens in /documents/:id → review/edit/refine → POST /documents/{id}/publish-assignment.
- Publish maps the contract's question blocks (
mcq/short/long/true_false/fill_blank) back toAssignment.questions(models/assessment.py:1182) via the existinghomework.pycreate path — preserving auto-grade, attempts, submissions, grading. - The sidebar Assignments entry is unchanged (
/homework→/assignments, management surface). The 3-stepCreateAssignmentPagewizard stays for fully-manual authoring; the card is the new AI-first entry. The currentAIGenerateModal(rawapi.post('/ai-teacher/generate-homework')) is superseded by the agent path through theservices/wrapper.
4.3 LLM consolidation (the "move to microservice" work)¶
- Migrate generation prompts out of
apps/backend/src/core/ai/prompts.pyinto the chatbot microservice:LESSON_GENERATION_PROMPT,HOMEWORK_GENERATION_PROMPT,KNOWLEDGE_CHECK_GENERATION_PROMPT, and the essay/project/case-study/etc. variants. - Replace
api/v1/ai_teacher.pyLLM calls (generate_lesson:528,generate_homework:670) withchatbot_clientproxy calls returning the document contract. - The
AIPanel(content-editor/ai/AIPanel.tsx) currently callsai_teacher— repoint it through the proxy; it becomes a thin client of the same microservice generation endpoint. - After migration,
prompts.pyretains only non-generation strings (if any) or is deleted; verify withgrep -r "from .*prompts import". - Dovetails with
backend_microservice_extraction_plan.mdPhase 0 (dependency slimming) — moving generation out lets us drop langchain-style LLM deps from the backend image.
5. Frontend (apps/web) changes — Phase 1 surface¶
5.1 Route + page¶
- Add
/documents/:id→DocumentEditorPage(lazy, inroutes/lazy-pages.ts+SharedRoutes.tsx, mirroringPresentationViewerPagewiring). - Two-pane layout: left = prompt/chat + refine; right =
NotionEditor.
5.2 Reuse the editor¶
- Configure
NotionEditorwith a newEditorMode = 'document'(content-editor/NotionEditor.tsx:77). - Map document contract
blocks[]↔ TipTap JSON. MCQ blocks can reuseKnowledgeCheckBlockextension; math via existingMathBlock. - Autosave: debounce
onSave(json, html)→PATCH /documents/{id}(same pattern asLessonEditorPageautosave).
5.3 AI panel (conversational, block-scoped)¶
- Adapt
content-editor/ai/AIPanel.tsxfrom form-driven to a small chat that targets the document: full-doc refine + selected-block refine ("regenerate this question", "make harder", "add 5 more MCQs"). - On block selection in the editor, panel shows block-scoped actions →
POST /documents/{id}/refine.
5.4 Chat integration ("Open in editor")¶
- Add a
DocumentCardinline artifact (model onPresentationCard.tsx) rendered inMessageBubble.tsxfor lesson_plan/question_bank/moduledonepayloads. - Card shows title + "Open in editor" → navigates to
/documents/:id(document is created ondoneviaPOST /documents). - Also fixes the flagged gap:
toolResultrendering path (currently stored, never rendered).
5.5 Documents list + export¶
/documentslist page (filter by type), empty-state CTA "Generate from chat".- Export buttons (DOCX/PDF/MD) →
GET /documents/{id}/export; gate downloads for B2C free via existingrenderDownload/DownloadLockButton.
6. Improved agent prompts (deliverables for the microservice)¶
These live in the chatbot microservice after consolidation. Drafted here so the microservice team has the spec. Goal: industry-standard pedagogy + structured, editor-ready output matching §3 contract. Replaces the thin placeholder behavior noted in memory (
lesson-plan-generator).
6.1 Lesson plan — system prompt (draft)¶
You are an expert instructional designer for higher-ed, corporate, and K-12 educators.
Produce a lesson plan using BACKWARDS DESIGN (objective → assessment → activities).
Always include, as ordered sections:
1. Title (concise, names the topic + level)
2. Learning Objectives — measurable, Bloom's-tagged verbs (apply/analyze/evaluate), 2–4 items.
3. Prerequisites — what learners must already know.
4. Assessments — Formative (in-class checks) AND Summative (graded), each tied to an objective.
For any quiz problems, include a worked answer key.
5. Lesson Flow — timed segments (e.g., 0–10 min hook, 10–30 min direct instruction, ...),
with instructor actions and learner actions per segment.
6. Materials & Resources.
7. Differentiation — support for struggling learners + extension for advanced.
8. Closure / Exit Ticket.
Rules:
- Match vocabulary to AUDIENCE ({audience}): K-12 → "students/class"; higher_ed → "students/semester";
corporate → "participants/session".
- Use the learner's curriculum context if provided ({rag_context}); cite it, don't invent standards.
- Write math in KaTeX ($...$).
- Output MUST conform to the document contract v1 (blocks with stable ids). No prose outside blocks.
- Be specific and classroom-ready; avoid filler like "engage students meaningfully".
6.2 Question bank — system prompt (draft)¶
You are an assessment specialist. Generate a question bank that is rigorous, unambiguous,
and ready to edit.
Inputs: topic, count, difficulty mix, question types, audience, language, optional {rag_context}.
For each question produce a contract block with: stem, type (mcq/short/long/true_false/fill_blank),
options (for mcq, exactly one unambiguously correct), answer, explanation, difficulty, Bloom level,
and the objective/topic it assesses.
Rules:
- Vary Bloom levels across the set unless the user requests one level.
- MCQ distractors must be plausible and represent common misconceptions — never "none of the above"
as filler.
- No trick questions; one defensible correct answer each.
- Math/chemistry in KaTeX ($...$).
- Group by topic/difficulty if the set is large; emit a final "Answer Key" section.
- Ground in {rag_context} when provided; do not fabricate standards or page numbers.
- Output MUST conform to the document contract v1. Each question is one addressable block (stable id)
so individual questions can be regenerated.
6.3 Lesson / module creation — system prompt (draft)¶
You are a curriculum author. Generate a self-contained learning MODULE (a teach-ready unit),
not just an outline.
Sections: Overview & learning outcomes; Concept explanations (with examples + analogies);
Worked examples; Knowledge-check questions (contract mcq blocks); Summary; Further reading.
Rules:
- Depth appropriate to {audience} and {duration}.
- Prefer concrete worked examples over abstract definitions.
- KaTeX for math; fenced code for programming topics.
- Ground in {rag_context} when provided.
- Output MUST conform to the document contract v1 with stable block ids.
6.4 Refine prompt (block-level, draft)¶
You are editing ONE block of an existing {doc_type}. Apply the instruction to the target block
ONLY; keep the surrounding document's style, difficulty band, and audience.
Return a single replacement block (same id, same kind unless the instruction changes type).
Instruction: {instruction}. Target block: {block_json}. Document meta: {meta}.
7. Phasing¶
Phasing here mirrors the unified roadmap in
../engineering/chatbot-agent-platform-migration.md §8
(this plan = the editor/WS2 view; the architecture doc = the full picture incl. presentation,
research, visuals, and supporting agents).
| Phase | Scope | Depends on |
|---|---|---|
| 0 | Consolidation: shared service_client; agent contract POST /v1/agents/<name>; document contract v1 incl. visual block kinds; extend chatbot_client with generate_document + refine_block. Backend holds no prompts. |
microservice contract sign-off |
| 1 (pilot) | Assignment card: POST /v1/agents/assignment; documents table + CRUD + export proxy; /documents/:id editor reusing NotionEditor; details → generate → editor → review/edit → Publish to Assignment. Sidebar management unchanged. Trainer-only. |
Phase 0 contract |
| 1b | Block-level refine via AI panel (regenerate question / make harder / add 5 MCQs). | Phase 0 refine endpoint |
| 2 | Lesson plan + question bank as document agents; DocumentCard "Open in editor"; read/edit/export. |
Phase 1 + validation study |
| 3 | Visuals: Mermaid MermaidBlock node + diagram blocks; agent→ChartBlock mapping; AI image agent tool → image blocks; SVG/chart export fidelity. |
Phase 2 |
| 2′ (later) | Learner read-only published view; version history; mobile parity. (Presentations stay on their own viewer; not folded in.) | Phase 2 validation study |
Validation gate: before broadening past the pilot (Phase 1 → 2), run the deferred 3-trainer "generate → refine → export" study (carried over from chat-first-ai-workspace.md).
8. Risks¶
- Contract drift — if the microservice returns inconsistent block structure, web↔TipTap mapping and refine break. Mitigate: version the contract, validate server-side, add a fallback "treat as single markdown block" path.
- Export fidelity for math — reuse doc-intelligence HTML→PDF; validate KaTeX-in-HTML export early in Phase 1.
- Prompt-migration regressions — moving
ai_teacherprompts may change output shape for existing lesson/homework generation. Mitigate: snapshot current outputs before migration; diff after. - Quota/entitlement leak — keep all gating in FastAPI proxy; microservice must not be reachable directly from web.
- Autosave data loss — debounced PATCH + explicit "saved" status + server timestamp; no silent catch.
9. Open questions (also in feature doc)¶
- [ ] Document route name:
/documents/:idvs/artifacts/:id. - [ ] Does
modulewrite back to coursecontent_blockson "Publish", or stay standalone? - [ ] Version history in Phase 1 or Phase 2?
- [ ] Where does the chatbot microservice live + how is the contract versioned/deployed? (blocks Phase 0)
- [ ] Math representation in contract: inline KaTeX strings (proposed) vs. dedicated math blocks.
10. Explicitly NOT in this plan¶
- No code is written in this round (plan only, per 2026-06-21 direction).
- No real-time collaboration (CRDT/Yjs).
- No presentation migration into the generic editor.
- No mobile (React Native) editor.
- No learner authoring.