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, and events.
- 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 an OpenAI-compatible adapter.
- 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 adapter handles:
- 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).
- Parameter filtering — dropping unsupported params with a logged warning instead of failing.
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.
- Event — Structured log entry. Types include
model_request,model_response,tool_call,tool_result,tool_error,manual_prompt,manual_response,hallucinated_tool_call, and more. - ManualResponseRecord — Captured human responses keyed by
(tool_version, args_hash, occurrence_index)for deterministic replay.
Frontend
The frontend is a single-page React application:
- Stack: React 18, TypeScript, React Router v6, TanStack React Query, Tailwind CSS.
- Pages: Tool Library, Tool Builder, Model Configs, Plans, Plan Builder, Sessions, Session Detail, Plan Stats.
- Live streaming: Sessions stream events via Server-Sent Events. The UI renders model 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. Dynamic tools imported from shared bundles are gated behind explicit user review and approval before they can execute.
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.