Files
learning-platform/AI_AGENT.md
RaymondVerhoef 07af2783dc
All checks were successful
On Push to Main / test (push) Successful in 1m33s
On Push to Main / publish (push) Successful in 1m31s
On Push to Main / deploy-dev (push) Successful in 2m3s
Add comprehensive documentation for key organizational aspects
- Introduced "Pension Scheme & Benefits" detailing secondary employment benefits and pension specifics.
- Created "Roles & Accountabilities" outlining the Holacracy role structure and responsibilities within Respellion.
- Added "Security" section covering GDPR compliance and workplace safety protocols.
- Established "Spending and Contracting" policy detailing expense categories and submission processes.
- Documented "Who We Are" to define Respellion's identity, services, and operational model under Holacracy and ISO 9001.
2026-05-27 08:24:56 +02:00

16 KiB
Raw Permalink Blame History

AI Agent Context Guide: Respellion Learning Platform

Welcome, fellow AI agent! If you are reading this, you are tasked with maintaining or extending the Respellion Learning Platform. This document provides the critical context, architectural patterns, and design standards you need to successfully work on this codebase.

Last updated: 2026-05-26 — Per-user curriculum start (employees enroll on first login; week/cycle derived from each user's curriculum_started_at, no longer from the ISO calendar week). Documents the real React/Vite + PocketBase stack, TF-IDF retrieval, 3 learning-content types, 3 micro-learning types, and the tiered Claude model setup.

1. Architectural Overview

This is a single-page React application built with Vite, backed by PocketBase as the database and auth layer. There is no separate backend server — the browser talks directly to PocketBase, and to the Anthropic API through a reverse proxy.

  • Frontend: React 19, React Router 7, Vanilla CSS (via CSS variables) + Tailwind v4 utilities mapped to those variables.
  • Backend: PocketBase (self-hosted, SQLite). All data is stored in PocketBase collections, not localStorage.
  • Animations: Framer Motion (page transitions, podium effects, gamification feedback).
  • Icons: Lucide React.
  • Visualizations: D3.js (used strictly for the Admin Knowledge Graph).
  • Retrieval: A dependency-free TF-IDF index over the knowledge graph (src/lib/retrieval.js). There is no Qdrant and no embeddings API — older specs that mention them describe a design that was never built.

The top-level app/ directory is abandoned Next.js scaffolding from that original design. It is not built or deployed. Ignore it; the real app is src/.

2. State Management & Storage (Critical)

All persistent data lives in PocketBase. The data access layer is in src/lib/db.js, which wraps the PocketBase SDK client from src/lib/pb.js.

PocketBase Collections (current):

  • topics — Knowledge graph nodes (id, label, type, description, learning_relevance, relevance_locked, theme, complexity_weight, difficulty).
  • relations — Knowledge graph edges (source, target, typerelated_to / depends_on / part_of / executed_by).
  • content — AI-generated learning modules per topic (topic_id, data). The data field is a merged JSON object containing only the content types generated so far (e.g. { article: {...}, slides: [...] }). New types are shallow-merged in by learningService.js; nothing is overwritten.
  • micro_learnings — Generated micro-learning artifacts (topic_id, type, content, status). One record per topic per type. status='published' items are visible to employees.
  • micro_learning_completions — Append-only completion events (team_member_id, micro_learning_id, topic_id, type, session_week).
  • curriculum_versions — Versioned 26-week schedules (version_number, status, generation_reason, confirmed_by, confirmed_at, schedule JSON, coverage_stats JSON). Exactly one active at a time.
  • leaderboard — Points ledger (user_id, name, points, tests_completed, learnings_completed).
  • team_members — Registered users with PIN auth (name, role, pin, curriculum_started_at, enrollment_status).
  • sources — Uploaded source documents and extraction status (name, status, error, progress).
  • settings — Key/value store (key, value).
  • llm_calls — Per-call telemetry (task, model, tier, duration_ms, token counts, stop_reason, ok, error_msg).

Dropped collections: quiz_banks, quiz_results, quiz_cache, learn_progress, and the legacy curriculum (v1) collection no longer exist. The matching db.js helpers are deprecated stubs — do not build on them.

localStorage is only used for admin browser settings (not user data):

  • admin:model:fast / admin:model:standard / admin:model:reasoning — per-tier model overrides (legacy admin:model still honored for standard).
  • admin:use_simulation — when true, callLLM returns stub data instead of calling Anthropic. Useful for UI work without spending tokens.
  • kb:suggestions — Pending/approved/rejected graph deltas proposed by R42. Always mutated via kbStore (see §9).
  • quiz:active:{userId} — Boolean flag set while a user is mid-quiz. R42's launcher is hidden when this is true (quiz-integrity rule).
  • chat:thread:{userId} — Persisted R42 conversation, capped at 50 messages.

Session: Login is PIN-based. The logged-in user's ID is stored in sessionStorage under respellion_session and resolved against team_members on app load (src/store/AppContext.jsx).

Per-user curriculum position (important — changed): Each employee starts the curriculum when they choose. On first login a blocking onboarding screen (/onboarding) records curriculum_started_at and flips enrollment_status to active. AppContext derives state.weekNumber — an absolute counter starting at 1 — from getPersonalWeekNumber(curriculum_started_at) (= floor(days_since_start / 7) + 1). The 26-week slot and cycle come from getCurriculumWeek(n) (((n-1) % 26) + 1) and getCurriculumCycle(n) (floor((n-1)/26)+1) in curriculumService.js. The cycle is detached from the ISO calendar — week 1 is simply the first 7 days after the user's start. After week 26 the cycle restarts at week 1 with the same content. state.weekNumber is 0 until the user enrolls.

Do not reintroduce ISO-week-based scheduling or a shared admin:current_week. There is no global "current week" anymore — every employee has their own.

Auto-Cancellation: The PocketBase JS SDK has auto-cancellation enabled by default, which aborts concurrent identical requests (common under React StrictMode and Promise.all) with ClientResponseError 0. It is globally disabled via pb.autoCancellation(false) in src/lib/pb.js. Never re-enable it.

PocketBase URL: Resolved from VITE_PB_URL, else window.location.origin (src/lib/pb.js). In production Caddy proxies /api/* and /_/* to the PocketBase container.

Important: All db.js functions are async. Always await them — omitting await silently passes a Promise where data is expected.

3. The AI Integration (Anthropic)

All Anthropic calls go through one wrapper.

  • Location: src/lib/llm.js, function callLLM(...). Callers must never reach /api/anthropic directly.
  • Proxy: Requests hit /api/anthropic/v1/messages. In Docker, Caddy proxies this to https://api.anthropic.com and injects the x-api-key header server-side. In local dev, vite.config.js does the same using process.env.ANTHROPIC_API_KEY. There is no client-side API key.
  • Model tiers: fast = claude-haiku-4-5-20251001, standard = claude-sonnet-4-6, reasoning = claude-opus-4-7. Choose a tier per task; admins can override per tier from Settings.
  • Structured output: Prefer Anthropic tool use with a forced toolChoice. Tool inputs are validated against Zod schemas in src/lib/llmSchemas.js (auto-looked-up via toolSchemaRegistry). For text responses, parseStructuredText strips code fences and extracts the outermost balanced JSON.
  • Prompt caching: Wrap stable system text with cachedSystem(text) to attach cache_control: ephemeral.
  • Retry/limits: src/lib/llmRetry.js handles exponential backoff with jitter on retryable statuses (408/425/429/5xx/529), honors Retry-After, and provides rate limiters (e.g. the extraction limiter caps ~20 req/min, burst 2). Default maxTokens is 4096; extraction and long-form content use 8192.
  • Telemetry: Every call is logged (best-effort, non-blocking) to the llm_calls collection.

4. Design System & Aesthetics

Respellion relies on a premium, modern aesthetic.

  • CSS Variables: Rely on the variables in src/index.cssvar(--color-bg), var(--color-paper), var(--color-teal), var(--color-accent), radii var(--r-sm), var(--r-lg), var(--r-org).
  • Tailwind: Tailwind v4 utilities are mapped to these variables. Use classes like bg-teal, text-fg-muted, border-bg-warm. Avoid raw hex codes.
  • stylesheet.css (repo root) is the authoritative visual reference and is frozen — do not edit it.
  • Components: Reuse the UI primitives in src/components/ui/ (Card.jsx, Button.jsx, Tag.jsx, Input.jsx).

5. Learning Content Types (on-demand, per topic)

src/lib/learningService.js generates three long-form content types into the content collection, on demand:

Type Schema key Description
article content.article Title, intro, sections, key takeaways
slides content.slides Slides with bullets and speaker notes
infographic content.infographic Headline, tagline, stats, steps, quote

generateLearningContent(topic, force, selectedType) accepts one of the three types, or 'all' (admin regeneration). Cache-hit logic checks content[selectedType] directly; new data is shallow-merged so other types are preserved. There is no podcast type.

Article refinement uses targeted patch tools (set_intro, set_section, add_section, remove_section, replace_takeaways) so the model edits only what changed.

6. Micro-Learnings (the weekly session)

src/lib/microLearningService.js generates short, single-topic interactions into the micro_learnings collection. Three types are currently active (a former reflection_prompt type was dropped):

Type Tier Shape
concept_explainer standard { sections: [{ title, content (HTML) }] } (≥3 sections)
scenario_quiz standard { scenario, options: [{ text, isCorrect, explanation }] } (34 options, 1 correct)
flashcard_set fast (Haiku) { cards: [{ front, back }] } (510 cards)

getOrGenerateMicroLearning(topicId, type) returns a cached published record if present, else generates and stores one. Completions are recorded via useMicroLearningCompletions into micro_learning_completions (append-only). A weekly session is "done" when every required topic has at least one completed micro-learning.

7. Weekly Test

src/lib/testService.js builds a 5-question multiple-choice quiz for the user's current week.

  • Primary topic comes from the active curriculum week (else a deterministic hash fallback), plus a few review topics for breadth.
  • Generated in one fast-tier batch call (emit_quiz_questions), with quality gates: no duplicate options, no banned fillers ("all of the above"), explanations ≥20 chars, and a check that correctIndex isn't dominated by one position.
  • Scoring: +2 points per correct answer (saveTestResultscore * 2), written to the leaderboard collection.

8. Gamification

If you are extending gamification (src/pages/Leaderboard.jsx):

  • Tests grant +2 points per correct answer (testService.js → saveTestResult).
  • Badges are computed at render time: First Steps (1 test), Veteran (5 tests), Perfectionist (a 100% score).
  • Points accumulate in leaderboard via db.upsertLeaderboardEntry. Admins are excluded from the public board.

9. R42 Chatbot

The platform ships a global chatbot avatar called R42, rendered as the Respellion { r } brand mark in three states (idle / typing / error).

  • Mark component: src/components/ui/Mark.jsx — pure SVG with state, size, theme, brace, letter props. Uses the BallPill font (@font-face in src/index.css), falling back to JetBrains Mono.
  • Chat module: src/components/chat/.
    • ChatLauncher.jsx — global FAB; auto-hides when quiz:active:{userId} is set; listens to the respellion:quiz-state window event.
    • ChatWindow.jsx — chat panel; renders messages from useChat; surfaces graph-delta confirmation chips.
    • useChat.js — owns the message list, persists to chat:thread:{userId} (cap 50; only the last ~12 turns are sent to the API), calls callLLM.
    • prompts.js — the cacheable system prompt blocks, greeting, and the propose_graph_delta tool spec (max 3 topics / 5 relations).
    • rag.js — builds KB context using the TF-IDF index from src/lib/retrieval.js (top-K topics + verbatim mentions), filters relations to retrieved topics, and validates proposed deltas (dedupe by id/label, no orphan/self relations, hard caps).
  • Model: R42 runs on the fast/standard Claude tier (Haiku/Sonnet); low latency matters for chat.
  • Quiz-integrity rule: src/pages/Testen.jsx sets quiz:active:{userId}=true on start and clears it on every non-quiz phase + unmount, dispatching respellion:quiz-state. Never bypass this — letting users ask R42 mid-quiz would break scoring.
  • Graph refinement: when R42 proposes a propose_graph_delta, rag.js validates it and a confirmation chip appears inline. Admin clicks YeskbStore.applyDelta writes to PocketBase immediately. Non-admin clicks YeskbStore.appendSuggestion queues a pending entry in kb:suggestions.
  • Admin approval UI: src/components/admin/SuggestionsQueue.jsx lets admins approve (re-runs applyDelta) or reject queued suggestions.
  • kbStore: src/lib/kbStore.js is the single source of truth for chatbot-path KB mutations. It dispatches respellion:kb-updated after writes so the D3 graph and queue refresh.

10. Admin Panel

src/pages/Admin/index.jsx is tabbed: Sources (upload + extraction), Content (review/refine generated content), Quizzes, Curriculum (generate/preview/activate a 26-week schedule), Graph (D3 knowledge graph + suggestions queue), Team (manage members), Settings (model overrides, simulation toggle, smoke-test reset). Source upload lives in src/components/admin/UploadZone.jsx (.txt / .md, ≤5 MB).

11. How to Add New Features

  1. Schema: add a PocketBase collection via the PB Admin UI or a migration in pb_migrations/ (and mirror it in scripts/setup-pb-collections.mjs).
  2. DB helpers: add async CRUD in src/lib/db.js.
  3. UI: build with Card, Button, Tag, and Framer Motion entry animations.
  4. Logic: connect to src/store/AppContext.jsx for global user/week context; otherwise keep state local.
  5. Verify: npm test, npm run lint, npm run build.

12. Known Gotchas & Decisions

  • Per-user curriculum start. Week/cycle derive from each user's curriculum_started_at, not the calendar. There is no shared current week. New users are gated through /onboarding until enrolled.
  • No podcast type. Three content types only: article, slides, infographic.
  • Three micro-learning types. concept_explainer, scenario_quiz, flashcard_set. reflection_prompt was dropped.
  • No Qdrant / no embeddings. Retrieval is local TF-IDF (src/lib/retrieval.js).
  • Caddy, not Nginx. The reverse proxy is Caddy (Caddyfile).
  • PocketBase auto-cancellation is OFF. Set in src/lib/pb.js; never re-enable.
  • Go through callLLM. Never call the Anthropic proxy directly; you lose retry, schema validation, and telemetry.
  • AI token budget. Truncation surfaces as LLMTruncatedError (stop_reason: max_tokens). For extraction, tighten the prompt's topic cap before raising max_tokens.

13. 26-Week Per-User Curriculum System

The platform uses a 26-week perpetual curriculum cycle. Every employee covers the knowledge base in focused, thematic weekly blocks, starting whenever they enroll.

  • Service: src/lib/curriculumService.js — week/cycle math, AI schedule generation, version lifecycle, progress.
  • DB: curriculum_versions holds generated JSON schedules; topics carry theme, complexity_weight, difficulty as generation input.
  • Version lifecycle: draftactivesuperseded. Only one active version at a time (CurriculumManager.jsx).
  • Per-user weeks: getPersonalWeekNumber(startedAt) yields an absolute week counter; getCurriculumWeek/getCurriculumCycle map it to the 126 slot and cycle. Same content each cycle.
  • Topic enrichment: a one-off AI step assigns theme/complexity_weight/difficulty to topics missing them before generation (enrichTopicsForCurriculum, batches of 20).
  • Progress: getYearProgress(userId, personalWeekNumber) computes completion for the current cycle.
  • Fallback: if no active version exists, getAssignedTopic() falls back to deterministic hash-based assignment. Keep the fallback.

Good luck! You are building a platform that empowers continuous learning. Keep it fast, keep it beautiful, and keep the user engaged.