The four layers
Layer 1 — The deterministic kernel (core/)
The kernel is the source of truth for every cell. It’s pure Python, has zero LLM dependencies, and could run as a headless grid service if you pulled the LLM layer off.
Key modules:
The kernel is deterministic: same inputs, same outputs, every time. That’s the property that makes the preview/apply flow trustworthy — the frontend can show you exactly what will change, and the backend will produce that exact result when you click Apply.
Layer 2 — The provider abstraction (core/providers/)
A thin interface that normalizes the four LLM SDKs into a single shape:
main.py never touches a provider SDK directly — it calls call_model(agent_id, ...) and the provider abstraction handles:
- Translating to the SDK’s specific call shape (
messages=[...]vscontents=...vs Claude’s system parameter) - Classifying errors (auth vs transient vs fatal) so retry logic can be generic
- Reporting a uniform
ProviderResponse(text + model + token counts + finish_reason)
catalog.py module lists every known model with its provider, display name, and metadata. The frontend reads this catalog through the /models/available endpoint to populate the model picker.
Layer 3 — Orchestration (main.py)
The FastAPI app that ties everything together. It:
- Accepts a user prompt + current sheet context over HTTP.
- Runs a router classifier on a small fast model to pick the right agent.
- Builds a system prompt from the selected agent’s
.jsondefinition + a live grid snapshot. - Calls the agent model, parses the JSON response, and previews the writes through the kernel.
- Returns the preview to the frontend for Apply/Dismiss.
- Router call — tiny, ~500-token prompt, pinned to the fastest small model (typically Groq’s
llama-3.1-8b-instant). Output: one lowercase agent id. - Agent call — the big one. Uses the user’s selected model. Input: full grid context + agent’s specialist prompt. Output: structured JSON with
target_cell,values,plan,chart_spec,intents, etc.
Layer 4 — The frontend (static/)
Vanilla HTML + JS + CSS — no framework. The full app is in:
static/index.html— the workbook view: grid canvas, chat composer, toolbar.static/landing.html— the hero-prompt entry page.static/app.js— all the client logic: cell rendering, chat, settings, marketplace, chart rendering via Chart.js.
The SaaS layer (cloud/)
A thin authentication + per-user-kernel-isolation layer that sits on top of the core. It’s optional — self-hosted GridOS doesn’t touch any cloud/ code. When enabled, it provides:
- Supabase JWT auth
- Per-user kernel isolation via a
ContextVar-bound kernel pool (LRU 64) - BYOK — per-user API keys stored server-side in a Supabase table with row-level security
- Tier enforcement (Free / Plus / Student / Pro / Enterprise) for monthly token + workbook-slot quotas
- Usage telemetry + analytics
uvicorn main:app --reload skips all of this — you get a single shared kernel and your API keys live in data/api_keys.json.
Why these layer boundaries
The narrow interfaces between layers make three kinds of work cheap:- Swapping an LLM provider — only
core/providers/changes. The kernel, orchestration, and frontend don’t know which SDK is underneath. - Swapping the frontend — the backend is all JSON over HTTP. You could wire a CLI, a CLI-TUI, a Discord bot, or a React SPA to the same API and every kernel feature would work identically.
- Extending the content layer — new formulas / agents / models slot in through the plugin system without touching core.
Where to read the code
If you want to understand the codebase start-to-finish, read in this order:core/models.py— the data shapes that flow through everything.core/engine.py— where cells actually live and get computed.core/providers/base.py— the LLM contract.main.py→generate_agent_preview— the one function that ties it all together.static/app.js→sendChatMessage— the frontend counterpart.