Architecture
Llaboratory is a two-service application: a Python backend (FastAPI + SQLite) and a TypeScript/React frontend (Vite + Tailwind CSS). The two communicate over a REST API with Server-Sent Events for live streaming.
System overview
│ React UI │────> FastAPI Backend │────> LLM Provider │
│ (Vite + TS) │ │ (Python + SQLite) │ │ (OpenAI API) │
│ :5173 │ │ :8000 │ │ │
└──────────────┘ └──────────────────────┘ └─────────────────┘
│
│
┌──▼──────────┐
│ SQLite DB │
│ (WAL mode) │
└─────────────┘
Backend
The backend is a Python FastAPI application with the following layers:
- API layer — REST endpoints for CRUD operations on tools, model configs, plans, sessions, events, and analysis/reporting.
- Service layer — Business logic: tool building, plan composition, session management.
- Agent loop — The core session runner that orchestrates model requests, tool execution, and event logging.
- Provider adapter — Normalizes all provider interactions to a common internal representation. v1 ships two adapters: the OpenAI-compatible Chat Completions adapter and the OpenAI Responses API adapter. Both emit the same normalized turn dict, so the agent loop, logging, and analysis are provider-agnostic.
- Analysis endpoints — Aggregate per-session and within-plan metrics, expose CSV export, and generate markdown findings reports for plan versions.
- Storage — SQLite with WAL mode for concurrent access. Migrations are versioned.
Provider adapter layer
All providers are normalized to one internal representation so the agent loop, logging, and analysis are provider-agnostic. The harness ships two adapters — a Chat Completions adapter (OpenAI / OpenRouter / LM Studio / Ollama) and a Responses API adapter (OpenAI / OpenRouter /v1/responses) — selected by the model config's provider_kind. Each adapter:
- Mapping normalized messages (
system,user,assistant,tool) to provider formats. - Streaming response parsing — assembling partial text and incremental tool-call arguments from deltas.
- Normalizing finish reasons, token usage, and error taxonomy (transient vs. permanent).
Data model
Key entities (all persisted to SQLite):
- Tool / ToolVersion — Logical tool with immutable versioned snapshots. Each version freezes the model-facing name, description, parameter schema, and response configuration.
- ModelConfig — Authoring template for provider connection details. Copied by value into plan versions.
- Plan / PlanVersion — Composable experiment definition. Pins tool versions, freezes model config, prompts, and run settings.
- Session — Single execution of a PlanVersion. Records status, timing, totals, and termination reason.
- RunBatch — Groups N sessions created from one Run in batch… request against a single PlanVersion. Tracks aggregate status and supports abort.
- Event — Structured log entry. Types include
session_start,model_request,model_response,tool_call,tool_result,tool_error,hallucinated_tool_call,loop_guard_triggered, andsession_end.
Data lifecycle endpoints
POST /export— Bundle selected (or all) tools, model configs, and plans — optionally with run history — into a ZIP.POST /import/check/POST /import— Dry-run conflict check followed by a committed import from a previously exported ZIP, with per-item rename support.POST /factory-reset— Irreversibly deletes all tools, model configs, plans, sessions, and batch runs.GET /seed/preview/POST /seed— Preview and load the 9 built-in sample tools, used by the onboarding checklist.GET /run-batches,POST /run-batches,GET /run-batches/{id},POST /run-batches/{id}/abort— Create, list, track, and abort batch runs.
Frontend
The frontend is a single-page React application:
- Stack: React 18, TypeScript, React Router v6, TanStack React Query, Tailwind CSS.
- Pages: Dashboard (onboarding), Tool Library, Tool Builder, Tool Detail/Stats, Model Configs, Plans, Plan Builder, Plan Versions, Plan Stats, Plan Report, Sessions, Session Detail, Batch Runs, Batch Detail, Data Transfer (export/import), and Factory Reset.
- Live streaming: Sessions stream events via Server-Sent Events. The UI renders model reasoning, text, and tool-call arguments incrementally as they arrive.
- API communication: All data fetching uses TanStack React Query for caching, deduplication, and optimistic updates.
Security
Dynamic tool code runs in-process without sandboxing. This is intentional for locally-authored tools in a single-user research harness.
API keys are supplied via environment variables and are never stored in the database or export bundles.
Concurrency
The batch runner executes at most 5 sessions concurrently. SQLite uses WAL mode so concurrent session writers don't serialize badly. Sessions beyond the cap wait in pending status.