Skip to content

Architecture

The agent is a single FastAPI backend, a Postgres store, and a small React client. It reads VEX events, grounds a reply in what a student is actually doing, and delivers that reply over a live stream. The design has two ideas at its core. Feedback flows through one shared pipeline no matter who started it, and the code is split into layers that each depend inward, never outward.

%%{init: {"flowchart": {"nodeSpacing": 40, "rankSpacing": 55}}}%%
flowchart LR
    hub[("Invite Institute Hub<br/>VEX logs")]

    subgraph API["FastAPI process"]
        direction LR
        ingest["ingest<br/>fetch + parse"] --> pg[("Postgres")]
        daemon["proactive daemon<br/>background thread"] --> pipe["feedback pipeline"]
        routes["api routes<br/>reactive lane"] --> pipe
        pipe --> pg
    end

    hub -. "poll" .-> ingest
    pg --> stream["SSE stream"] --> client["React client"]

Two Lanes, One Pipeline

A student can be helped two ways, and both run the exact same feedback code.

  • Reactive. A student types a message or taps the help button. An API route grounds the reply and answers.
  • Proactive. A background daemon watches the event stream, measures how each run of code differs from the last, and detects behaviors worth a nudge. When one fires it pushes a short note on its own.

These used to be two hand-copied sequences that drifted apart. They are now the single function in services/feedback.py, so a proactive nudge and a typed answer share the same grounding, the same model call, and the same pedagogy. The only difference is the input. See the feedback pipeline for the step-by-step.

The Layers

The backend package is server/vex_agent, organized so each layer only reaches inward.

Layer Package Holds
API api/ FastAPI routers for students, the SSE stream, admin, and system, plus the Turnstile bot gate
Services services/ orchestration, the shared feedback pipeline, the proactive daemon, sessions, identity, and log sync
Domain domain/ pure pedagogy with no framework or database, the situation model, the prompt, the feedback policy, and per-playground grounding
Data data/ Postgres access
Ingest ingest/ fetch VEX logs from the Hub and parse them into rows
LLM llm/ the OpenAI-compatible client and the response sanitizer
Triggers triggers/ the vendored behavior engine, edit distance, detectors, and episodes

The domain/ layer is the valuable part to keep clean. It has no imports from FastAPI or psycopg, so the rules for what the agent says can be read and tested on their own.

The Trigger Engine Is Vendored

Everything under triggers/ is copied from lm-dashboard and kept in sync on purpose. The dashboard is where researchers watch these same behaviors, so sharing the engine keeps the agent's read of a student comparable to the numbers on the board. The coupling points one way. The app adapts its event stream to the engine in services/proactive.py, and the engine itself stays free of any framework or database.

Processes And Topology

Compose runs two containers, connected through Postgres.

Service Command Role
api uvicorn vex_agent.app:app serves the reactive routes and the SSE stream, and hosts the proactive daemon as a background thread
db postgres the store, and the seam between writing and reading

The proactive daemon is not its own container. It starts inside the API process on startup and stays off unless TRIGGER_DAEMON_ENABLED is set. It runs in a thread rather than an asyncio task because its database, HTTP, and model calls are blocking and would otherwise stall the event loop. Because it keeps a cursor and dedupes each trigger, it assumes exactly one instance is running.

Grounding Is Deterministic

The agent never asks the model what the robot did. It builds a situation model from the telemetry itself, a plain-language summary computed in domain/context_builder.py, and hands that to the one feedback call. A single LLM pass, grounded on measured facts and the student's real program, means there is no second paraphrase step that can drift into a confident wrong story. This is the lesson the early spike taught, and it is why the pipeline looks the way it does.