Skip to content

Chatbot Agent Platform — LLM/RAG Migration Plan

Status: Draft / proposed · Owner: Backend · Created: 2026-06-21 Scope: Move all LLM- and RAG-calling features out of apps/backend into the kwiloai_chatbot microservice as specialized agents, while keeping the product UI and its API contract unchanged.

1. Why this exists

Today apps/backend is both the API for the web/mobile UI and the place where LLM/RAG calls happen — inline in route handlers (ai_teacher.py, ai_tutor.py, presentation.py, mock_test.py, …). Only the chat itself has been migrated to the chatbot service. Every other AI feature (assignment generation, lesson generation, mock tests, presentations, content suggest/improve, knowledge checks, insights/summaries) still runs in-process.

Consequences we want to remove: - The LLM gateway, providers, prompts, RAG, and JSON-repair logic are duplicated conceptually between webapp and chatbot, and drift over time. - Bugs in generation surface as backend 500s (e.g. the coding-assignment JSON extraction failure, fixed in PR #1100) instead of being owned by the AI service. - Provider/model strategy, retrieval, and tool routing live in two places.

Target: the chatbot becomes a multi-agent platform; the webapp becomes a thin orchestration layer (auth, quota, persistence, AI-content labeling, usage logging) that proxies to agents. The frontend does not change.

2. Decisions (locked)

# Decision Choice Rationale
D1 Agent contract One signed endpoint per agent (POST /v1/agents/<name>) Strong typing, independent versioning, per-feature dark-launch flags.
D2 Gating vs. LLM boundary Webapp owns ALL gating (auth, RBAC, quota, entitlements, AI-content labeling) + result persistence; chatbot owns ALL LLM work incl. token-usage persistence. Cost/USD dropped. Gating resolves from org plan/role/subscription + compliance tables in the webapp DB, so it stays at the policy layer. pydantic-ai normalizes usage() across providers, so the chatbot is the natural owner of token logging. Cost gated nothing (quota is count-based) and only fed admin dashboards — store raw tokens, drop USD.
D3 RAG ownership Chatbot owns all retrieval It already owns chat RAG (NCERT + institution KB); one retrieval owner avoids embedding-space drift.
D4 Starting point Fix the live assignment bug first (done, PR #1100), then migrate assignment generation as the pilot agent Highest user pain, self-contained, proves the full pattern incl. RAG.
D5 Card ≠ uniform output Every dashboard card becomes a chatbot agent, but they land on two output surfaces: document artifacts (lesson plan, question bank, assignment) → the generic two-pane editor; specialized artifacts (slide deck, research) → their existing dedicated viewers. The cards are not interchangeable. Lesson plan / question bank / assignment are editable rich documents → reuse the TipTap editor. A slide deck is an HTML deck and research is a long-running cited report — each already has the right surface; forcing them into the document editor loses affordances. See §3.5.
D6 Slide deck + research = agents Move presentation generation into the chatbot as an agent (UI unchanged). Make deep research a chatbot agent and retire the standalone kwiloai_deep_research microservice — but gated on a risk evaluation (see §12). Consolidation per D2/D3. Presentation is low-risk (already a self-contained generation call). Deep-research retirement is the highest-risk move (10-min streaming, distinct auth, heavy deps deliberately isolated for event-loop reasons) → sequenced last and gated.
D7 Assignment becomes a card Replace the wizard's "fill form → manually add / AI-generate questions" step with: enter details → AI generates directly → opens in the editor → review/edit/publish. Surface it as a dashboard card. The sidebar Assignments entry stays as the management surface. Today AI-gen is a buried optional modal inside a 3-step wizard (AIGenerateModal). Promoting it to a card makes generation the primary path. The assignment stays a structured gradeable entity (questions, submissions, grades) — the editor's question blocks map back to Assignment.questions on Publish to Assignment; it is not a free-text document.

The boundary, one line: the webapp is the policy layer (decides who's allowed and how much) and the chatbot is the LLM layer (does all generation). Nothing about gating leaves the webapp; nothing about generation stays in it. The LLM never runs unless the webapp gate said yes — gating is always checked before the proxy.

3. Current-state inventory

All call sites below are in-process in apps/backend (the chat flow is already external). Global prefix: /api/v1.

# Feature Endpoint Style RAG Quota/Usage
1 Lesson generation POST /ai-teacher/generate-lesson non-stream, free text Qdrant FULL_CHAPTER CONTENT_GENERATION
2 Assignment/homework gen POST /ai-teacher/generate-homework non-stream, JSON, 3-retry Qdrant CHUNKS_WITH_CONTEXT CONTENT_GENERATION
3 Suggest content POST /ai-teacher/suggest-content non-stream, JSON Qdrant CHUNKS_ONLY CONTENT_GENERATION
4 Improve content POST /ai-teacher/improve-content non-stream, free text optional CONTENT_GENERATION
5 Knowledge-check gen POST /ai-teacher/generate-knowledge-check non-stream, JSON none (inline content) CONTENT_GENERATION
6 Tutor suggestions GET /ai-tutor/suggestions non-stream, line-split none none
7 Insight extraction POST /ai-tutor/conversations/{id}/insights + auto non-stream, JSON none none
8 Conversation summary internal (chat_history) non-stream, free text none none
9 Moderation dead code (0 callers) non-stream none none
10 Mock-test generation POST /ai-tutor/mock-test/generate non-stream, JSON Qdrant none
11 Mock-test grading POST /ai-tutor/mock-test/{id}/submit N calls/loop, JSON none none
12–15 Presentation HTML/JSON/regen/improve /presentations* non-stream HTML+JSON (SSE progress) none PRESENTATION
(legacy) In-process eval grading dead — superseded by doc-intelligence

Excluded unless explicitly added later: image/video generation (Imagen/Veo/DALL·E) — different modality.

Already external: chat → chatbot svc · subjective/physical-exam evaluation → doc-intelligence svc · deep research → deep-research svc.

3.5 Card→agent→surface mapping

This is the user-facing translation of the inventory: each trainer dashboard card (and the "Assignment" card we are adding, D7) maps to one chatbot agent and one output surface. The surface column is the key correction to "every card becomes an editor document" (D5).

Card Mode / entry today Chatbot agent (target) Output surface Migration note
Lesson plan lesson_plan chat mode → already external chatbot /v1/chat/stream POST /v1/agents/lesson-plan two-pane editor (/documents/:id) Already chatbot-side; formalize as a typed agent returning the document contract + open in editor.
Question bank question_bank chat mode → already external chatbot POST /v1/agents/question-bank two-pane editor Same as above; each question is one addressable block for refine.
Assignment (new card, D7) buried AIGenerateModal inside the 3-step wizard → in-process /ai-teacher/generate-homework POST /v1/agents/assignment two-pane editor → "Publish to Assignment" Card flow: details → generate → editor → review/edit → publish to the gradeable assignments table. Sidebar Assignments (management) unchanged.
Slide deck presentation mode → in-process /presentations/generate-stream (SSE) POST /v1/agents/presentation (streaming) existing /presentations/:id HTML viewer (unchanged) Move generation to the agent; UI/output surface stays.
Research research mode → standalone kwiloai_deep_research svc (SSE proxy) POST /v1/agents/research (streaming, long-run) existing /research library (unchanged) Fold into chatbot + retire the standalone service — gated on §12 risk eval.
(supporting) knowledge-check, suggest, improve, mock-test gen+grade, insight, summary, suggestions various in-process ai_teacher / ai_tutor one agent each n/a (inline) Per the phases in §8.

Visuals (locked): the document contract blocks[] (see the editor plan, §3) carries visual block kinds so document agents can return text and visuals: math (KaTeX), chart (Recharts data), table, image (AI-generated via an agent image tool), and diagram (Mermaid — net-new editor node). The editor already renders images/math/charts/tables/embeds; net-new is a Mermaid TipTap node, agent→ChartBlock mapping, and export-fidelity for SVG/charts. This revises §3's "image/video excluded" line for the document path: image generation is relocated into a chatbot agent tool, not excluded. (Standalone Imagen/Veo media gen for non-document use is out of scope here.)

Side bug to fix in passing: the Research tile navigates to /teach/research, which the route guard rejects (research isn't in teacherIntentSchema) and bounces back to /teach — so the tile is effectively dead today. Wire it to start a research session like the other tiles.

4. Move / Stay / Shared

Concern Verdict Notes
LLM gateway + providers (core/ai/llm.py, providers/*) MOVE Agent platform core. Brings _is_fallback_eligible chain + observability. No DB deps.
Prompts (core/ai/prompts.py) MOVE Belong with the agents.
JSON repair (core/ai/json_utils.py) MOVE Carry the PR #1100 fix into the agent.
RAG retrieval (rag_service.py → Qdrant) MOVE (D3) Joins chat RAG already owned by chatbot.
Gating: auth, RBAC, quota, entitlements (ai_quota.py, route deps) STAY (D2) The policy layer. Coupled to org plans, role matrix, subscription tables in the webapp DB. Always checked before proxying.
Token-usage logging (ai_usage_logger.py) MOVE (D2) Chatbot persists token usage from pydantic-ai usage() (normalized across providers). Webapp stops writing ai_usage_logs.
Cost / USD pricing (inline per-token rates) DROP (D2) Gated nothing — quota is count-based (ai_quota.py:260); USD only fed admin dashboards. Remove the inline math, don't migrate it.
AI-content labeling (is_ai_generated, AIEvaluation) SHARED contract Labels live on webapp tables (DPDP/POCSO). Agent response must carry label intent; webapp sets on persist.
Moderation (moderation.py) MOVE + wire up Currently dead; co-locate with the gateway and actually enable if it's a compliance gate.
apps/rag (standalone pgvector/Vertex job) RETIRE Already backfilled into Qdrant; not a runtime dependency.

5. Target architecture

Frontend (unchanged)
   │  same REST/SSE endpoints
apps/backend  — policy layer (thin orchestration) —
   │  ALL gating: auth · RBAC · quota · entitlements · AI-content labels · persistence
   │  S2S HMAC + opportunistic OIDC  (chatbot_client pattern)   [gate BEFORE proxy]
kwiloai_chatbot  — LLM layer / agent platform —  (separate repo, not this monorepo)
   ├─ chat agent (exists)
   ├─ assignment-gen · lesson-gen · knowledge-check · suggest/improve
   ├─ mock-test (gen + grading)
   ├─ presentation (HTML/JSON/regen/improve)
   ├─ insight + summary · suggestions
   └─ moderation (shared gate)
        owns: LLM gateway · providers · prompts · RAG (Qdrant) · JSON repair
             · token-usage logging (pydantic-ai usage())

6. Agent contract & transport

Reuse the existing chatbot S2S signing (apps/backend/src/core/chatbot_s2s.py): HMAC over METHOD\npath\ntimestamp\nsha256(body) → headers X-Kwilo-S2S-Key-Id, X-Kwilo-S2S-Timestamp, X-Kwilo-S2S-Signature; add Google OIDC only when the target is *.run.app. The signed body must be the exact wire bytes.

Two transport shapes (most features are request/response, not SSE): - Request/response — model on the doc-intelligence client (doc_intelligence_client.py): retry on 502/503/504 + connect/timeout with cold-start backoff, wall-clock budget cap, typed errors → 502/503/504. - SSE — model on _stream_chat_via_chatbot (ai_tutor.py), incl. the finalize-once-on-completion-or-disconnect pattern (presentation progress, future streaming).

Endpoint shape (D1): POST /v1/agents/<name> returning structured JSON:

{
  "result": { /* questions[] | slides[] | html | summary | ... */ },
  "labels": { "is_ai_generated": true }
}
Webapp then persists result, applies labels, and calls increment_usage (a count, not dollars) on quota. Token usage is logged inside the chatbot from pydantic-ai usage() — it is not returned for the webapp to persist. No usage field, no cost. (The webapp may rely on a 2xx as the success signal for the count increment.)

Prerequisite (Phase 0): there is no shared signed-HTTP helper today — chatbot / doc-intel / deep-research each reimplement auth + timeouts. Build apps/backend/src/core/service_client.py (sign + OIDC + retry + typed errors) before fanning out ~10 new calls, or the inconsistency triples.

7. Cross-cutting details

  • Quota stays webapp-side: AIQuotaService.check_quota(user, feature) before proxy; increment_usage(user, feature) (count, no cost) after success. Reuse the chat finalize-once pattern so a chatbot failure or client disconnect does not double-charge.
  • Token usage moves entirely to the chatbot (pydantic-ai usage(), normalized across providers); the webapp stops writing ai_usage_logs. Cost/USD is dropped — it gated nothing and only fed admin dashboards — so the inline per-token rates in ~5 places (ai_teacher.py:1019, presentations.py:157/680/787, config.py:400) are removed, not centralized. No pricing module.
  • Gating granularity (batch boundary). Gating is always checked before the proxy, and for batched LLM work the batch is the gating unit, not the item. Mock- test grading (today a per-question LLM loop) and "add 5 more MCQs" gate once for the whole call, let the chatbot do all N generations, then persist. Never gate or call the LLM per-item inside a DB transaction (risk #6).
  • AI-content labeling (DPDP 2023 / POCSO): is_ai_generated flags + AIEvaluation records live on webapp-owned tables. Make label intent part of the agent response so the webapp keeps labeling correctly.
  • RAG coherence: qdrant_vector_size must equal rag_embedding_dimension (Bedrock Cohere Embed v4 @ 1536 today). KB ingestion is already external (eval-service via Azure Service Bus); read + write sides must agree on provider/model. Note: docstrings in apps/rag, rag_service.py, tools/rag.py, and evaluation.py still say "pgvector / 768 / Vertex" — that is stale; live truth is Qdrant / 1536 / Bedrock Cohere.

8. Migration phases

This unifies two workstreams that ship together: (A) LLM consolidation (move generation into chatbot agents) and (B) the agent document editor (the editable two-pane surface + visuals). They are sequenced so each phase delivers user-visible value, not just plumbing.

  • Phase 0 — Foundations. Shared service_client helper (sign + OIDC + retry + typed errors — replaces the 3 hand-rolled clients today); agent response contract (POST /v1/agents/<name>result + labels, no usage; token logging in the chatbot; streaming variant for long-run agents); the document contract v1 with visual block kinds (math/chart/table/image/diagram, stable block ids); flag/fallback convention (<feature>_enabled factory → per-route 503). No pricing module — cost is dropped. Blocked on chatbot-owner sign-off (§10.1).
  • Phase 1 — Pilot: Assignment card + editor (D7). The pilot proves the whole pattern (agent + editor + structured persistence) end-to-end on the highest-pain flow:
  • Backend: POST /v1/agents/assignment (carry the PR #1100 JSON fix; teacher-gen RAG moves to chatbot). New documents table + CRUD + /documents/:id editor route (reuse NotionEditor).
  • Frontend: new Assignment dashboard card → enter details → generate → editor → review/edit → Publish to Assignment (maps question blocks → Assignment.questions). Sidebar Assignments management surface unchanged. Dark-launch behind a flag.
  • Phase 1b — Block-level refine. "regenerate this question / make harder / add 5 more MCQs" via POST /documents/{id}/refine → chatbot. Batched, gated once per call (§7).
  • Phase 2 — Lesson plan + question bank as document agents. POST /v1/agents/lesson-plan and /question-bank return the document contract; DocumentCard "Open in editor" in chat; read/edit/export in the editor. (Both already live chatbot-side as chat modes — this formalizes the typed agent + the editable surface.)
  • Phase 3 — Visuals (locked full scope). Mermaid TipTap node + agents emit diagram blocks; agent→ChartBlock mapping for chart blocks; AI image generation relocated into a chatbot agent tool, embedded as image blocks; export fidelity — SVG/Recharts rasterization in the DOCX/PDF/PPTX path (doc-intelligence).
  • Phase 4 — Slide deck as agent (D6, low risk). Move presentation HTML/JSON/regen/improve (largest tokens, ~16K; SSE progress) into POST /v1/agents/presentation. UI unchanged — output still opens /presentations/:id.
  • Phase 5 — Research as agent + retire the deep-research microservice (D6, HIGH RISK). Add a streaming, long-run research agent to the chatbot; switch the webapp /deep-research/* proxy's downstream target from the standalone service to the agent (frontend + persistence + gating untouched); then decommission kwiloai_deep_research. Gated on the mitigations in §12.
  • Phase 6 — Supporting agents: mock-test (collapse the per-question grading loop into one batched agent call — gate once per batch, persist mastery after; never gate/call per-question inside the grading transaction, §7); knowledge-check, suggest, improve; tutor suggestions, insight extraction, summary.
  • Phase 7 — Cleanup: wire up moderation as a shared gate; retire apps/rag and the dead in-process eval grading code.

Validation gate (carried from agent-document-editor.md): before broadening past the pilot (Phase 1 → 2), run the deferred 3-trainer "generate → refine → export" study. Q4 validation is flagged weak; the pilot is the cheapest way to close it.

9. Risks & difficulties

  1. Two-repo change set. Every phase touches kwiloai_webapp + kwiloai_chatbot. Contract drift is the #1 risk → version the agent contract, add contract tests.
  2. Billing accuracy — resolved by dropping cost. Cost gated nothing (quota is count-based) and only fed admin dashboards, so USD computation is removed rather than migrated; there is no ai_usage_logs.cost_usd left to break. Token counts move to the chatbot via pydantic-ai usage().
  3. RAG ownership split. Embedding-space coherence + already-external KB ingestion make provider/model config a coupling landmine.
  4. Compliance regression. Forgetting to signal is_ai_generated breaches DPDP/POCSO labeling silently → make it part of the contract.
  5. Latency + cold starts. Extra network hop on synchronous generation; mitigate with retry/backoff + budget cap; mind Cloud Tasks 300s ceilings on long ops.
  6. Transactional interleaving. Some features call the LLM inside a DB transaction (summary commits mid-flow; grading loop updates mastery). Restructure to call-then-persist.
  7. Quota double-charge. Increment happens after the call; reuse the chat finalize-once guard.
  8. Net-new scope. docs/backend_microservice_extraction_plan.md explicitly keeps the AI tutor in the backend and has no chatbot-agent phase — update it so the team isn't working off a contradictory plan.
  9. Moderation is dead code. "Moving" it isn't enough; it must be wired in.

10. Open questions

  • Is moderation a launch/compliance requirement (i.e., must Phase 6 actually enable it), or is it deferred?
  • Do we migrate image/video generation too, or leave media in the webapp?
  • Where do prompts live canonically once moved — versioned in the chatbot repo, or a shared package?
  • Human-oversight for AI grading: EvaluationStatus has no explicit "teacher-reviewed/overridden" state — is that enforced in workflow/UI, and does it need a hard DB gate?

10.1 For the kwiloai_chatbot repo owner (sign-off needed — blocks Phase 0)

This boundary makes the chatbot the LLM layer for every feature, so the following are the chatbot owner's calls. Phase 0 can't finalize the contract without them.

Agent contract - Will the chatbot expose POST /v1/agents/<name> returning exactly {result, labels} (no usage), behind the existing S2S HMAC (+ OIDC for *.run.app)? Any deviation from chatbot_s2s.py's signing scheme? - How is the contract versioned (path /v1, header, or per-agent), and who owns the contract tests that guard against drift (risk #1)? - Per-agent result shapes (questions[], slides[], html, summary, …) — chatbot-defined schema, or jointly owned? Where is it published so the webapp can validate?

Token-usage persistence (now chatbot-owned) - Where does the chatbot store token usage (table/schema), and does it need the webapp to pass tenant identity (user_id / org_id / feature) on every call so usage is attributable per org? Without that the chatbot can log tokens but not whose. - Does the chatbot have its own DB, or does usage logging need a shared store?

RAG (now chatbot-owned) - Does the chatbot already have access to the institution KB + NCERT Qdrant collections, and does it agree on provider/model/dimension (Bedrock Cohere Embed v4 @ 1536, not the stale "Vertex/768")? - For teacher-gen RAG, does the webapp pass curriculum context (subject/chapter/grade IDs) and the chatbot resolves retrieval, or does the chatbot need direct access to curriculum tables?

LLM strategy - Does the chatbot's pydantic-ai setup replicate the webapp's provider fallback chain (_is_fallback_eligible: Gemini → Claude → GPT-4o), and which providers are wired today?

Reliability / labeling - Are agent calls idempotent under the webapp's 5xx retry, so a retry doesn't double-generate or double-log usage? Need an idempotency key in the contract? - Will every agent response carry labels.is_ai_generated, and is it ever false (i.e. is the flag always true for generated content)? - Streaming: which agents stream (SSE) vs. plain JSON — does the chatbot support the finalize-once-on-disconnect pattern for the streaming ones (presentations)?

Moderation - Does the chatbot want to own moderation as a shared pre/post-gate, and is enabling it a launch/compliance requirement (ties to risk #9 + the moderation open question above)?

11. Key references (file:line)

  • HMAC signing: apps/backend/src/core/chatbot_s2s.py:26
  • OIDC + X-Service-Key: apps/backend/src/services/doc_intelligence_client.py:41
  • OIDC helper: apps/backend/src/core/cloud_run_auth.py:11
  • Cloud-Run-aware signed POST: apps/backend/src/services/chatbot_client.py:166
  • Payload/entitlement template: apps/backend/src/services/chatbot_client.py:99
  • SSE proxy + finalize-once: apps/backend/src/api/v1/ai_tutor.py:985
  • Feature-flag factory: apps/backend/src/services/doc_intelligence_client.py:457
  • Assignment generation (pilot): apps/backend/src/api/v1/ai_teacher.py:669
  • JSON extraction (fixed): apps/backend/src/core/ai/json_utils.py:207
  • RAG retrieval: apps/backend/src/services/rag_service.py:537
  • Deep-research SSE proxy: apps/backend/src/api/v1/deep_research.py:72,126
  • Deep-research persistence + publish-as-lesson: apps/backend/src/services/deep_research.py:156, apps/backend/src/services/lesson_converter.py
  • Deep-research session table: apps/backend/src/models/deep_research.py:40

12. Deep-research microservice retirement — risk evaluation

Decision context (D6): fold deep research into the chatbot as an agent and retire the standalone kwiloai_deep_research service. This section is the requested risk evaluation; it gates Phase 5.

What's already easy (low risk)

The integration is a clean signed-HTTP proxy with all policy webapp-side, exactly matching the target boundary (D2): - Frontend is unaffected. The browser only ever calls webapp endpoints (/deep-research/start, /sessions, /quota, /publish, /download). Swapping the proxy's downstream target is transparent to apps/webservices/deep-research/index.ts need not change. - Persistence stays. The deep_research_sessions table lives in the webapp DB; the service is stateless. The webapp already creates the row, checkpoints progress, saves results to GCS, and does publish-as-lesson into content tables. None of this moves. - Gating stays. Count-based, webapp-side: Capability.CHAT_RESEARCH + B2C (1/30d) or institutional AIFeatureType.DEEP_RESEARCH (role-based daily). Untouched.

Risks (and why "retire the service" ≠ "merge into the chat process")

# Risk Severity Mitigation
R1 Run duration vs transport ceiling. Deep-research runs 2–10 min; the chatbot client read timeout is 300s (chatbot_client.py:206). A request/response agent ({result, labels}) cannot hold a 10-min run. High Research must be a streaming, long-run agent (SSE + finalize-once), not the default JSON contract. Confirm the chatbot's SSE relay supports multi-minute streams + heartbeats end-to-end before cutover.
R2 Different auth scheme. Deep-research uses OIDC + X-Service-Key; the chatbot uses S2S HMAC (+ opportunistic OIDC). Medium Resolve in Phase 0 via the shared service_client (sign HMAC + add OIDC for *.run.app). The research agent must accept the unified contract.
R3 Event-loop contention — the original reason for extraction. The service was split out because long-running multi-LLM + web-crawl work "occupies event-loop resources" (backend_microservice_extraction_plan.md:152). Merging that workload into the chat-serving process re-introduces the exact problem — a heavy research run could degrade interactive chat latency. High Do not co-locate the workload in the chat process. "Agent in the platform" ≠ "same Cloud Run service." Recommended: deploy the research agent as a separate Cloud Run service from the same chatbot codebase/agent-framework (shared contract + prompts, isolated process + autoscaling). This gets consolidation (one codebase, one contract, no separate auth/deps drift) without coupling chat latency to research load.
R4 Heavy, distinct dependency surface. Tavily, arxiv/academic search, multi-LLM, GCS, DOCX/visual generation. Pulling these into the chat image bloats cold-start and blast radius. Medium Same as R3 — separate deployment target keeps the chat image lean. If truly one image, measure cold-start regression first.
R5 Stateful post-completion pipeline. Unlike chat (stream text, done), research has a side-effect chain: DB write → DOCX export (doc-intelligence) → GCS upload → optional publish-as-lesson. The {result, labels} contract has no model for "produce a 10-min file artifact + persist a session." Medium Keep the result-handling in the webapp proxy (it already does this). The agent streams progress + the final report payload; the webapp owns the artifact/session pipeline exactly as today.
R6 Blast radius / availability coupling. If research and chat share a service, a research-triggered crash or memory spike takes down chat. Medium → Low with R3 Separate deployment (R3) keeps failure domains independent.
R7 Stale docs mislead implementers. docs/DEEP_RESEARCH_FEATURE.md describes a long-dead in-process Tavily+Pinecone design; model fields still say "Perplexity". Low Mark DEEP_RESEARCH_FEATURE.md stale; live truth = the proxy + kwiloai_deep_research. Fix the misleading field/docstring labels in the same change.

Recommendation

Proceed, but as the LAST migration (Phase 5) and as a logical consolidation, not a process merge. The high-value win — one agent contract, one auth scheme, one prompt home, no client drift — is captured by making research conform to the unified agent platform. The high risk is entirely in co-locating a 10-minute, dependency-heavy workload inside the interactive chat process, which would undo the very isolation it was extracted for (R3/R4/R6). Deploy the research agent as a separate Cloud Run service built from the chatbot codebase unless a cold-start + latency measurement proves a single image is safe. Cut over behind a flag with the standalone service kept warm for rollback; decommission only after the agent path is proven on real runs.

Changelog

  • 2026-06-21 — Initial draft. Captures inventory, the four locked decisions (D1–D4), move/stay/shared matrix, phased plan, and risks. Companion to the live bug fix in PR #1100 (assignment-generation JSON extraction).
  • 2026-06-21 — Sharpened the boundary. Revised D2: webapp owns ALL gating (auth/RBAC/quota/entitlements/AI-labeling); chatbot owns ALL LLM work incl. token-usage persistence (pydantic-ai usage()). Cost/USD dropped — removed the pricing module from Phase 0 and the usage field from the agent contract ({result, labels}). Added the batch-gating boundary (gate once per batch, not per item) to §7 and Phase 3. Risk #2 downgraded (cost no longer migrated). Added §10.1 — sign-off questions for the kwiloai_chatbot repo owner (contract, token storage + tenant identity, RAG access, provider fallback, idempotency, streaming, moderation) as a Phase 0 blocker.
  • 2026-06-24 — Folded in the dashboard-cards direction. Added D5 (card ≠ uniform output: documents → editor, slide deck/research → dedicated viewers), D6 (presentation
  • research become agents; deep-research microservice retired, gated), D7 (Assignment becomes a card: details → generate → editor → review/edit → Publish to Assignment; sidebar management stays). Added §3.5 card→agent→surface mapping + the locked full visuals scope (math/chart/table/image/Mermaid) — this revises §3's "image excluded" line for the document path (image gen is relocated into a chatbot agent tool, not dropped). Rewrote §8 into a unified 8-phase roadmap (LLM consolidation + editor + visuals, assignment as the pilot). Added §12 — deep-research retirement risk evaluation (recommendation: logical consolidation, separate deployment target, not a chat-process merge).