Back to featured work
Agentic RAG · Local-First AIPrototype

AAOS — Academic AI OS

Answer questions about university Moodle courses from the student's own course files, with cited sources, on a local machine.

62-second AAOS demo. Real recordings of AAOS with voiceover: answers with sources in English and Arabic, study tasks, Calendar events you approve, the dark theme and Arabic interface, and Telegram alerts.
Problem
Course answers scattered across Moodle; chatbots answer from the web, not your courses.
What I built
A local-first assistant that answers from your own course files with cited sources.
My role
Project author: backend, Moodle sync, RAG and verification, agent loop, dashboard.
Result
Runs against a real Moodle account, backed by about 4,250 backend and 264 frontend tests.
Hybrid RAGNLI-verified citationsFastAPI APIRead-only Moodle syncEnglish + ArabicApproval-gated writes

Evidence

More recorded proof

AAOS Ask page showing an answer about KNIME, a past-course label, and three cited sources from a STAI 401 study-notes PDF with page numbers.

01 / 06

Cited answer from a past course

Recorded Ask AAOS answer to “what is KNIME based on my past courses?”. The reply is labelled as coming from an archived course and lists the file, page, and version of each source. It was captured with the optional Gemini model selected, which is why the top bar shows the cloud badge.

Screenshot 1 of 6: Cited answer from a past course

Interface

One System. Two Experiences.

The AAOS Overview page in light and dark mode. Drag across the image, or focus the handle and use the arrow keys, to compare them.

The same AAOS Overview page in the dark theme
AAOS Overview page in the light theme: the ask box and this semester's course cards
LightDark

Problem

What this project had to solve

Course material is scattered across Moodle courses, files, and announcements, and generic chatbots answer from the open web instead of what a student was actually taught. AAOS answers from the student's own course files with citations, or says it does not have enough evidence.

Role / contribution

What I owned in the system

Project author responsible for the Python backend, Moodle connector, document pipeline, RAG and verification, agent orchestration, approvals, notifications, and the Next.js dashboard, built in gated phases with written acceptance criteria.

System context

Everything AAOS touches, on one page

AAOS architecture: the student's browser dashboard calls the FastAPI API and orchestrator on the student's PC; the orchestrator asks LM Studio for proposals only, runs typed tools and RAG over SQLite and a SHA-256 document store; a monitor and a read-only Moodle connector sit alongside; Telegram is send-only and Google Calendar is written only after human approval.
AAOS system context. The diagram from the AAOS README. Everything inside the box runs on the student's own PC; Moodle is read-only, Telegram is send-only, and Calendar writes wait for approval.

Architecture flow

System flow from input to output

  1. 01

    Read-only Moodle sync

    What happens

    Playwright drives a dedicated Edge profile to sync courses, assignments, deadlines, announcements, and files without ever writing to Moodle.

    Why it matters

    A sync is used only when it is complete, so downstream answers and alerts never act on a partial snapshot.

    • Playwright
    • Microsoft Edge
  2. 02

    Document ingestion

    What happens

    Files are stored by SHA-256 hash, and text is extracted from PDF and Word files with page numbers kept for every passage.

    Why it matters

    Page-level provenance lets every answer point back to the exact file and page it came from.

    • pypdfium2
    • python-docx
    • SQLite
  3. 03

    Intent routing and agent loop

    What happens

    A deterministic English and Arabic router sends known intents to typed tools; other questions enter a bounded agent loop where the model proposes tool calls.

    Why it matters

    Common questions need no model guess, and model proposals are validated against a closed schema before anything runs.

    • FastAPI
    • LM Studio
  4. 04

    Hybrid retrieval

    What happens

    BGE-M3 dense search in Chroma is combined with SQLite FTS5 lexical search over the course passages.

    Why it matters

    Dense and lexical search cover each other's misses on mixed English and Arabic course material.

    • BGE-M3
    • Chroma
    • SQLite FTS5
  5. 05

    Citation and NLI verification

    What happens

    The answer's claims must cite evidence IDs, and an NLI model verifies each claim against its passage; unsupported claims are dropped.

    Why it matters

    The system answers with sources or says it lacks evidence, instead of inventing deadlines, files, or facts.

    • mDeBERTa NLI
    • Transformers

Engineering decisions

Tradeoffs that shaped the build

  1. Decision 01

    Local-first by default

    The database, documents, search index, and models run on the user's machine; the optional Gemini cloud model is off by default and confirmed in the app before use.

  2. Decision 02

    The model proposes, code decides

    Model output, retrieved text, and tool results are treated as untrusted input; typed tools validate arguments and permissions before executing.

  3. Decision 03

    Humans approve external writes

    A Google Calendar event is only created after the user approves a durable approval record, and unclear outcomes are shown as unknown rather than success.

Under the hood

Explore the engineering

Four short excerpts from the AAOS source, copied verbatim from the public repository. Line numbers match the linked commit on GitHub.

DRAGOX7/aaos @ 621288d

Intent routing

src/aaos/agent/intent_router.py L375–415

GitHub

A deterministic English/Arabic router sends known questions to typed tools. Anything that writes is never routed, so it has to go through the agent loop's permission checks.

def route_intent(question: str) -> RoutedIntent:
    """Classify a question into one closed intent, or UNROUTED.

    Precedence: write/task/calendar requests are never routed; announcements
    or changes combined with each other or with a summary are ambiguous;
    changes outrank entity words; announcements outrank document and
    assignment words; a summary needs a document reference to be a document
    summary; a document reference outranks assignment and course facts.
    """
    text = normalize_arabic(question)
    if not text or has_bilingual_write_intent(text) or _TASK_OR_CALENDAR.search(text):
        return RoutedIntent.UNROUTED
    if asks_about_assistant(question):
        return RoutedIntent.UNROUTED

    summary = _SUMMARY.search(text) is not None
    document = _DOCUMENT.search(text) is not None or _COURSE_DETAILS.search(text) is not None
    announcements = _ANNOUNCEMENTS.search(text) is not None
    changes = _CHANGES.search(text) is not None
    course_facts = _COURSE_FACTS.search(text) is not None
    assignments = _ASSIGNMENTS.search(text) is not None

    if (changes and (announcements or summary)) or (announcements and summary):
        return RoutedIntent.UNROUTED
    if changes:
        return RoutedIntent.MOODLE_CHANGES
    if announcements:
        return RoutedIntent.ANNOUNCEMENTS
    if summary:
        if assignments and not document:
            return RoutedIntent.UNROUTED
        return RoutedIntent.DOCUMENT_SUMMARY if document else RoutedIntent.COURSE_SUMMARY
    if document:
        return RoutedIntent.DOCUMENT_QUESTION
    if assignments:
        return RoutedIntent.ASSIGNMENT_FACTS
    if course_facts:
        return RoutedIntent.COURSE_FACTS
    if _CONCEPT.search(text) and not _PERSONAL.search(_PAST_COURSES.sub("", text)):
        return RoutedIntent.DOCUMENT_QUESTION
    return RoutedIntent.UNROUTED

Technical implementation

How the system is built

A browser-based connector syncs courses, assignments, deadlines, announcements, and files from Moodle read-only through a dedicated Edge profile, and only complete snapshots are used.

Downloaded files go into a SHA-256 content-addressed store; PDF and Word text, including Arabic, is extracted with page-level provenance.

Retrieval combines BGE-M3 dense vectors in Chroma with SQLite FTS5 lexical search, and an mDeBERTa NLI model checks every cited claim before it is shown.

A local chat model served by LM Studio proposes actions as JSON; a closed schema and typed tools decide what actually runs.

aaos — Local setup and run

# Install backend and dashboard dependencies

uv sync

cd frontend; npm ci; cd ..

# Create the SQLite database in a data folder outside the repo

mkdir ..\aaos-data

cd ..\aaos-data

..\aaos\.venv\Scripts\python.exe -m alembic -c ..\aaos\alembic.ini upgrade head

# Check the environment

..\aaos\.venv\Scripts\aaos.exe doctor

# Start the FastAPI backend and Next.js dashboard (from the repo root)

.\scripts\start-aaos.ps1

# Moodle change check, scheduled every 15 minutes (07:00-24:00)

aaos monitor run-once

Stack

Core tools and system layers

PythonFastAPISQLiteLM StudioQwen3.5-4BBGE-M3ChromamDeBERTa NLIPlaywrightNext.jsTypeScript

Project explainer

Ask about this project

A case-study guide to the architecture and decisions behind AAOS — Academic AI OS.