Based on: Gemini Deep Research validation (2026-03-30) + 24 Graphiti decision records + S27 GDA 10 decisions Path A: Phase1 COMPLETE, Phase2 COMPLETE, Phase3 = this PRD, Phase4 = separate PRD
User confirmed P0 highest priority: "click tip/error to jump to original context"
- Add
sourceCanvasId?: stringandsourceNodeId?: stringtoTipIteminterface infrontend/src/services/api-client.ts - Add
sourceCanvasId?: stringandsourceNodeId?: stringtoWeaknessIteminterface infrontend/src/services/api-client.ts - Acceptance Criteria:
- TypeScript compiles without errors after adding fields
- Fields are optional (backward compatible with existing data)
- Update the FastAPI Pydantic response model for
/api/v1/profile/{nodeId}/summaryto includesource_canvas_idandsource_node_idfields - Extract these fields from Neo4j/Graphiti metadata when building the response
- Acceptance Criteria:
- GET
/api/v1/profile/{nodeId}/summaryresponse includessource_canvas_idandsource_node_idfor each tip and weakness item - Fields are null when source data is unavailable (graceful degradation)
- GET
- In
LearningProfile.tsxorReviewItem.tsx, implement onClick handler that callsgoToCanvas(item.sourceCanvasId)thensetSelectedNodeId(item.sourceNodeId) - Acceptance Criteria:
- Clicking a tip/error item with valid sourceCanvasId navigates to the correct canvas
- The target node is selected/highlighted after navigation
- Items without sourceCanvasId show no navigation action (no crash)
GDA-2: cancel textbook + cross-canvas search. RAG 6 channels to 4 channels.
- Remove
backend/app/services/cross_canvas_service.py - Remove all imports and dependency injection bindings referencing CrossCanvasService
- Acceptance Criteria:
- File does not exist after change
grep -r "cross_canvas" backend/app/returns zero results (excluding tests and docs)- Backend starts successfully without errors
- Remove any
textbook_retrieverchannel from RAG pipeline configuration - Verify only 4 channels remain: LanceDB, Vault Notes, Graphiti, Multimodal
- Acceptance Criteria:
grep -r "textbook_retriever" backend/app/returns zero results- RAG pipeline configuration lists exactly 4 channels
- Backend starts and
/api/v1/healthreturns 200
- Remove
vault_notesfrom DEFAULT_TABLES array if it appears alongside a dedicated vault_notes retrieval node - Acceptance Criteria:
vault_notesis not queried twice in a single search request- Hybrid search returns results without duplicates
GDA-4: prompts must be in external files, not inline Python strings
- Create
backend/app/prompts/exam/layer3.mdwith a template that accepts: node_content, node_type, effective_proficiency, mastery_label, student_tips, error_history, edge_reasons, conversation_summary - Acceptance Criteria:
- File
backend/app/prompts/exam/layer3.mdexists - Template contains placeholders for all 8 context variables
- Template is valid Markdown
- File
- Modify
question_generator.py_format_acp_layermethod to readlayer3.mdand inject variables via.format()instead of inline Python f-strings - Acceptance Criteria:
_format_acp_layercalls_load_prompt_file("layer3.md")- No inline prompt strings remain in the method (only variable injection)
- Unit test verifies the formatted output contains all context data
generate_questiontool still produces valid exam questions
Tier 2 search silently returns empty because fulltext index is not provisioned
- Add initialization routine to create
episode_contentfulltext index during FastAPI lifespan startup - Use:
CREATE FULLTEXT INDEX episode_content IF NOT EXISTS FOR (n:EpisodicNode) ON EACH [n.content] - Acceptance Criteria:
- After backend startup, Neo4j contains the
episode_contentfulltext index SHOW INDEXESin Neo4j includesepisode_content- Tier 2 search returns results for known keywords
- After backend startup, Neo4j contains the
- Ensure
search_memoriesinmemory_service.pycorrectly falls through Tier 1 then Tier 2 then Tier 3 - Add logging to indicate which tier produced results
- Acceptance Criteria:
- When Graphiti is available: Tier 1 returns semantic results
- When Graphiti times out: Tier 2 returns keyword results from fulltext index
- When both fail: Tier 3 returns in-memory cache results
- Total latency < 2 seconds
jieba tokenizer code exists but hybrid search is not activated
- Change default
query_typefrom"vector"to"hybrid"in LanceDB search configuration - Acceptance Criteria:
- Default search mode is
"hybrid"(not"vector") - Search queries use both dense vectors and FTS scoring
- Default search mode is
- Ensure
_jieba_tokenize()is called on both index-time text and query-time input - Acceptance Criteria:
- Chinese query "Bayes theorem in Chinese" returns relevant notes
- English queries still work correctly
- Unit test with Chinese text confirms tokenization and retrieval
GDA-3: group_id = canvas name. Currently a static default value for all canvases.
- Frontend must send the active canvas name when calling memory/search APIs
- Backend must use this as the
group_idparameter for Graphiti operations instead of a static default - Acceptance Criteria:
record_learning_memoryuses the canvas name as group_idsearch_memoriesfilters by the active canvas group_id- Different canvases produce isolated memory namespaces
- No static group_id strings remain in backend/app/ (excluding tests/config defaults)
- Convert canvas name to lowercase, strip special characters (e.g., "CS 188" becomes "cs188")
- Acceptance Criteria:
- Canvas named "CS 188" produces group_id "cs188"
- Canvas named "Linear Algebra" produces group_id "linearalgebra" or "linear-algebra"
- Normalization is consistent between frontend and backend