{
  "markdown": "<div align=\"center\">\n\n# Lumen\n\n**Type a one-sentence learning goal — an AI orchestrator builds you a private course in ~50 seconds,\na RAG tutor with citations teaches it, and you can audit every agent decision it made.**\n\n<sub>Custom multi-agent system, no LangChain · public evals with the weak scores kept in · live in production</sub>\n\n[![CI](https://img.shields.io/github/actions/workflow/status/ahmedEid1/lumen/ci.yml?style=flat-square&label=CI)](https://github.com/ahmedEid1/lumen/actions/workflows/ci.yml)\n[![authoring eval 3.85/5 (n=10)](https://img.shields.io/badge/authoring%20eval-3.85%2F5%20(n%3D10)-2ea44f?style=flat-square)](https://lumen.ahmedhobeishy.tech/eval)\n[![MCP registry](https://img.shields.io/badge/MCP%20registry-io.github.ahmedEid1%2Flumen-blue?style=flat-square)](https://registry.modelcontextprotocol.io/v0/servers?search=io.github.ahmedEid1%2Flumen)\n[![License: MIT](https://img.shields.io/badge/license-MIT-yellow?style=flat-square)](LICENSE)\n\n[**Live demo**](https://lumen.ahmedhobeishy.tech) · [**Eval results**](https://lumen.ahmedhobeishy.tech/eval) · [**Architecture**](docs/architecture.md) · [**MCP server**](docs/mcp.md)\n\n<img src=\"docs/screenshots/gifs/define-build.gif\" alt=\"Real production recording. A one-sentence goal becomes a learning brief, then the authoring orchestrator builds a 4-module, 16-lesson private course.\" width=\"900\">\n\n<sub>Real production recording (Groq Llama 3.3 70B). Intake trimmed 6×, the ~50 s build 16× — the brief and the finished course are real-time.<br>\nTry it yourself: [the one-click demo](https://lumen.ahmedhobeishy.tech/demo) pre-fills `demo@lumen.test` / `Demo!2026` and drops you into the tutor (free-tier box — give a cold page a few seconds).</sub>\n\n</div>\n\n## What this is\n\nA **learner-owned, two-role e-learning platform** — every signed-in user runs the whole loop themselves; `admin` only moderates and configures. The product is the loop; the point of the repo is the agentic system underneath it.\n\n| Step | What happens |\n|---|---|\n| **Define** | A guided AI intake (capped at six turns) turns a fuzzy goal into a structured **learning brief** — the source goal is field-encrypted at rest |\n| **Build** | The authoring orchestrator builds a **private course** from the brief — honest status, no half-finished partials, re-runnable, cancellable ([`build.py`](apps/backend/app/services/build.py), the durability/idempotency/quota shell) |\n| **Learn** | A **course-scoped RAG tutor** answers with lesson citations and a visible tool-call trace |\n| **Share** | Publishing stays private; public listing is an explicit share + admin moderation state machine with an immutable audit trail |\n| **Clone** | Any listed course can be remixed into your own draft, with server-written \"Based on …\" provenance and a sanitized export (no enrollments, traces, or soft-deleted content) |\n| **BYOK** | Bring your own model key (OpenAI / Anthropic / Groq / Mistral) — allowlisted providers, server-owned base URLs, envelope-encrypted write-only keys |\n\nShipped to production as **2.0.0-two-role** ([CHANGELOG](CHANGELOG.md)) — built as a gated waterfall: requirements → design → 6 ADRs → seven build streams, each cleared a Codex challenge, an independent Claude review, and a live in-browser walk before merge.\n\n## The agentic layer\n\nEvery item below is on production today, with the code one click away.\n\n### Custom multi-agent orchestrator — no frameworks\n\nThe tutor picks per-turn among five sub-agents in [`tutor_subagents/`](apps/backend/app/services/tutor_subagents/) — `retriever`, `web_searcher`, `code_runner`, `quiz_generator`, `concept_explainer` — under a hard cap on tool-call rounds ([streaming variant](apps/backend/app/services/tutor_orchestrator_stream.py)). The authoring side runs a six-stage pipeline — researcher → outliner → critic → reviser → lesson-drafter → final-critic — in [`authoring_orchestrator.py`](apps/backend/app/services/authoring_orchestrator.py), capped at six revise/critic calls.\n\n<div align=\"center\">\n<img src=\"docs/screenshots/gifs/tutor.gif\" alt=\"The RAG tutor streaming an answer: question sent, retriever sub-agent fires with its latency visible, answer streams in (2× speed)\" width=\"900\">\n\n<sub>Production recording at 2× — the retriever fires (latency on-screen), then the answer streams.</sub>\n</div>\n\n### Course-scoped RAG with citations, behind one authorizer\n\nRetrieval is scoped per course and routed through a single ACL clause ([`visibility.py`](apps/backend/app/services/visibility.py), ADR-0029) so private and cloned courses never leak chunks. Embeddings via Cloudflare Workers AI (`bge-small-en-v1.5`, 384-dim) into `pgvector`; answers cite specific lesson chunks.\n\n### Every agent decision is auditable\n\nEach LLM call logs prompt/completion tokens, USD cost, latency, and outcome to the `llm_calls` table ([`llm_call_log.py`](apps/backend/app/services/llm_call_log.py)); each agent step lands in [`agent_tracer.py`](apps/backend/app/services/agent_tracer.py). Learners get a per-turn **\"show me how you got this\"** drill-down — planner steps, tool calls, retrieval audits with similarity scores; authors get a step-by-step **build replay**.\n\n<div align=\"center\">\n\n| Tutor-turn trace | Authoring build replay |\n|---|---|\n| <img src=\"docs/screenshots/agent-trace.png\" alt=\"Trace drill-down: step-by-step timeline with planner, tool calls, retriever and synthesiser steps, plus retrieval audit cards showing per-chunk similarity scores\" width=\"440\"> | <img src=\"docs/screenshots/studio-replay.png\" alt=\"AI authoring replay: 8 orchestrator steps with per-step durations and the researcher step's prompt and response expanded\" width=\"440\"> |\n\n</div>\n\n### Eval harness with LLM-as-judge — published whole\n\nThree golden suites (30-item tutor, 10 authoring, 10 ingest) under [`evals/`](apps/backend/evals/), judged 0–5 per axis, plus adversarial probes. A 3-item smoke gates every PR ([workflow](.github/workflows/pnpm-eval-smoke.yml)); results are public at [/eval](https://lumen.ahmedhobeishy.tech/eval).\n\n**The point isn't the scores — it's the harness**: LLM-as-judge applied honestly to one strong subsystem and two early ones, every number reproducible with `make eval suite=…` and smoke-gated in CI.\n\n| Suite | Judged | LLM-judge score | Reading |\n|---|---:|---:|---|\n| Authoring | 10 / 10 | **3.85 / 5** | Strong — the headline number, backed by [the raw JSONL](docs/eval/authoring-n10-groq-20260525.jsonl) |\n| Tutor | 10 / 30 | 2.33 / 5 | Early — citation-format mismatch between the judge's expected citations and what the retriever pulls; 20 items skipped, cause documented |\n| Ingest | 4 / 10 | 0.83 / 5 | Early — v1 chunker emits one module per video; 6 items failed upstream transcript fetch before judging |\n\nThe weak scores are published whole, on purpose. [Methodology](https://lumen.ahmedhobeishy.tech/eval/methodology) · [raw reports](docs/eval/).\n\n### MCP server on the official registry\n\n[`app/mcp/`](apps/backend/app/mcp/) exposes nine tools (catalog, RAG tutor, FSRS reviews, AI authoring, ingest) over stdio + HTTP with OAuth client-credentials, published as [`io.github.ahmedEid1/lumen`](https://registry.modelcontextprotocol.io/v0/servers?search=io.github.ahmedEid1%2Flumen). Write tools gate on the `can_author` capability; URL ingest stays admin-only + flag-gated (ADR-0025).\n\nWiring it into an MCP client takes one JSON block:\n\n<details>\n<summary><b>Use it from Claude Desktop / Claude Code</b></summary>\n\n```json\n// claude_desktop_config.json\n{\n  \"mcpServers\": {\n    \"lumen\": {\n      \"command\": \"python\",\n      \"args\": [\"-m\", \"app.mcp\", \"--transport\", \"stdio\"],\n      \"env\": {\n        \"LUMEN_MCP_AUTH_TOKEN\": \"<client-secret from `make mcp-token`>\",\n        \"DATABASE_URL\": \"postgresql+asyncpg://lumen:lumen@localhost:5432/lumen\"\n      }\n    }\n  }\n}\n```\n\nOr for Claude Code: `LUMEN_MCP_AUTH_TOKEN=<secret> claude mcp add lumen -- python -m app.mcp --transport stdio`, then ask *\"list my Lumen courses\"* and watch `list_courses`, `ask_tutor`, `create_course_draft`, … fire. Full operator guide: [docs/mcp.md](docs/mcp.md).\n\n</details>\n\n### BYOK without the foot-guns\n\nPer-credential 256-bit DEKs wrapped by a versioned server KEK ([`secrets_crypto.py`](apps/backend/app/core/secrets_crypto.py)); decryption only inside the dispatch path — never in logs, traces, exports, or admin views. A [prod boot guard](apps/backend/app/core/prod_guards.py) refuses to start with stored credentials but no real KEK (ADR-0027). Request-count quotas close the `$0`-BYOK bypass of the dollar budget guard.\n\n### Zero-downtime phased migrations with evidence gates\n\nThe two-role rebuild migrated `student`/`instructor` → `user` live: widen accepted roles → backfill → drop old values only after access tokens drained their 15-min TTL. The Alembic chain enforces one phase per run, and migration-level evidence gates refuse to tighten constraints over un-backfilled rows — one of them caught a real prod data condition (85 legacy chunks with NULL embedding model) at deploy time.\n\n## Architecture\n\nThe short version: one FastAPI service owns all invariants, the agents are plain service-layer code, and every LLM call crosses one provider seam and one cost meter.\n\n```mermaid\nflowchart LR\n    user([User · authors + learns])\n    admin([Admin · moderates + config])\n\n    subgraph App[Application]\n      web[Next.js 15 · RSC]\n      api[FastAPI · Python 3.13<br/>capability-based authz<br/>central is_publicly_listed authorizer]\n      worker[Celery worker + beat]\n    end\n\n    subgraph Agents[Agent layer]\n      define[Goal intake → brief]\n      authoring[Authoring orchestrator<br/>researcher → … → final-critic]\n      tutor[Tutor orchestrator<br/>+ 5 sub-agents]\n    end\n\n    subgraph Data[Data]\n      pg[(Postgres 17 + pgvector)]\n      redis[(Redis 7)]\n      s3[(MinIO S3)]\n    end\n\n    subgraph LLM[Swappable LLM layer]\n      provider{LLM_PROVIDER dispatch}\n      groq[Groq · Llama 3.3 70B]\n      byok[BYOK allowlist<br/>OpenAI · Anthropic · Mistral · Groq]\n    end\n\n    subgraph Eval[Eval loop]\n      golden[(Golden datasets)]\n      judge[LLM-as-judge 0–5]\n      meter[llm_calls<br/>tokens · cost · latency]\n    end\n\n    user --> web --> api --> pg\n    admin --> web\n    api --> redis & s3 & worker\n    api --> define --> authoring\n    api --> tutor --> pg\n    mcp[MCP server · 9 tools] --> api\n    authoring & tutor --> provider\n    provider -.platform.-> groq\n    provider -.per-user.-> byok\n    api --> meter\n    golden --> judge --> provider\n```\n\nProvider-agnostic by env var — the live demo runs Groq's free tier; users dispatch on their own allowlisted keys. Every call crosses the cost meter, so budgets, quotas, and observability behave identically across providers. Full topology: [docs/architecture.md](docs/architecture.md).\n\n**Stack:** Python 3.13 · FastAPI · async SQLAlchemy 2 · Alembic · Celery — Next.js 15 · React 19 · TypeScript 5 · Tailwind 4 · TanStack Query — PostgreSQL 17 (`pgvector` + `tsvector`) · Redis 7 · MinIO — Docker Compose · GitHub Actions · Trivy + CodeQL + gitleaks · Caddy 2.\n\n## How it's built and tested\n\nThe process is the portfolio as much as the code. Every build stream cleared three gates before merge:\n\n1. **Codex challenge** — a second-brain CLI attacks the design, plan, and code; findings triaged and resolved.\n2. **Independent Claude review** — a gating review subagent re-checks against source until clean.\n3. **Live evidence** — drive the app as a real user in a browser, locally and on prod, on top of unit/E2E/a11y suites. Running-the-app evidence is required, not optional.\n\nAt the 2.0.0 release: **backend 1,421 tests / frontend 468 tests**, all green; WCAG 2.2 AA axe-core gate (11 surfaces, 0 violations); en + ar i18n parity; visual-regression baselines; Playwright E2E on Chromium **and** WebKit. The UI itself went through a 20-loop redesign (30+ Radix-backed primitives, ⌘K command palette, dark/light themes) with five in-loop Codex rescue passes plus a final Codex review. A green `main` auto-deploys to production.\n\n<div align=\"center\">\n<img src=\"docs/screenshots/gifs/cmdk.gif\" alt=\"Command palette: Ctrl+K opens it over the dashboard, typing filters live course search results alongside navigation, theme, and account commands\" width=\"700\">\n\n<sub><kbd>⌘K</kbd> — navigate, search courses, switch theme, sign out.</sub>\n</div>\n\n## Run it locally\n\nPrereqs: Docker Desktop 4.30+ (or Engine 27 + Compose v2).\n\n```bash\ngit clone https://github.com/ahmedEid1/lumen.git\ncd lumen\ncp .env.example .env\nmake up && make migrate && make seed\n```\n\nOpen <http://localhost:3000> and sign in:\n\n| Role | Email | Password |\n|---|---|---|\n| admin | `admin@lumen.test` | `Admin!2026` |\n| user | `teacher@lumen.test` | `Teach!2026` |\n| user | `student@lumen.test` | `Learn!2026` |\n\nWithout an LLM key the AI features fall back to a deterministic `noop` provider — the rest of the app still works. For the real thing (define/build, tutor, evals), a free Groq key is enough:\n\n```env\nLLM_PROVIDER=openai\nOPENAI_API_BASE=https://api.groq.com/openai/v1\nOPENAI_API_KEY=<your-groq-key>\nLLM_MODEL=llama-3.3-70b-versatile\n```\n\nThe same `LLMProvider` abstraction takes native Anthropic or OpenAI by env var — no code changes. Feature flags (`FEATURE_BYOK_ENABLED`, `FEATURE_PRIVATE_PUBLISH_ENABLED`, `CLONE_ENABLED`, `FEATURE_TUTOR_STREAMING`) default **off**; set them in `.env` once their prerequisites (e.g. a real BYOK master key) are in place. `make demo-seed` adds the richer agentic-demo bundle.\n\n<details>\n<summary><b>More screenshots</b> — dashboard, catalog, the agent-replay home page, the public eval page, a freshly built course, the brief review</summary>\n\n| | |\n|---|---|\n| ![Learner dashboard with in-progress courses and the \"create a course to learn\" entry](docs/screenshots/dashboard.png) | ![Public catalog with subject filters and tag rail](docs/screenshots/catalog.png) |\n| ![The home page: an agent-replay hero that walks through a real tutor turn](docs/screenshots/home.png) | ![Public eval page: honest scores with a sealed-run badge](docs/screenshots/eval-page.png) |\n| ![A freshly built private course in the three-column learn workbench](docs/screenshots/built-course.png) | ![Brief review: level, time budget, outcomes — nothing builds until you confirm](docs/screenshots/brief-review.png) |\n\n</details>\n\n## Status, limits, and honesty\n\n- **Live** at [lumen.ahmedhobeishy.tech](https://lumen.ahmedhobeishy.tech) — 2.0.0-two-role, shipped 2026-06-06. Free tier end-to-end: Groq + Cloudflare Workers AI + one AWS t4g.small (runbook: [docs/deployment/aws-vps.md](docs/deployment/aws-vps.md)). Budget guards and request quotas cap spend; expect free-tier latency under load.\n- **Email verification is off in prod** (`EMAIL_ENABLED=false` — no SMTP configured). A flag, not a code limitation.\n- **Eval scores include the weak ones**, with causes documented — see the table above and [docs/eval/](docs/eval/) for raw JSONL.\n- **Test counts are release-time snapshots** (the suites keep moving with `main`); CI is the live source of truth.\n\n## Built by\n\n**Ahmed Hobeishy** — AI / Agent Engineer in Essen, Germany. Lumen started as a 2020 Django side-project; five years and one model revolution later it's the centrepiece of my agentic-AI work: agents that are **measured** (golden evals, LLM-as-judge), **auditable** (per-call cost/latency traces, citation checks), and **shipped** (live, CI-gated, self-hostable).\n\n**Open to AI / Agent Engineer roles in Germany where evaluation and observability are first-class.**\n\n[LinkedIn](https://www.linkedin.com/in/ahmedhobeishy/) · [GitHub](https://github.com/ahmedEid1) · or open an issue here.\n",
  "bytes": 15630,
  "sha": "cb655fb15ecd78e7cb895e0f51dd1be86d1e9093242f01a48b689106d69c9777",
  "repo_slug": "ahmedeid1/lumen",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_ahmedeid1_lumen_e1a63798/readme"
}