Repository navigation
api
Active contributors: Zachary Bensalem
The API is the transport between the terminal client (or any other client) and a
Fleet process. It is a single FastAPI application created by
src/fleet_rlm/api/app.py. It owns no reasoning logic: every route either reads
durable state from PostgreSQL through the Store, or asks a SessionRuntime to do
something, including running a Turn.
The API exposes five capabilities:
- Session discovery, configuration, mode, model selection, context, and history.
- A Session's workspace files and attachments.
- Memory and Skill synchronization.
- One streaming Turn endpoint.
- A health endpoint used to decide whether an already-running server can be reused.
There is no caller authentication. The server binds to 127.0.0.1 by default,
and a non-loopback bind is an explicit opt-in that still does not authenticate
callers. See Security for the trust boundary.
create_app in src/fleet_rlm/api/app.py takes optional settings, an optional
Store, and two flags (initialize_db, configure_observability). The Store is
not opened at construction time. The lifespan acquires it, so importing the app
or generating the OpenAPI document does not touch PostgreSQL, Daytona, or
MLflow.
The lifespan does, in order:
- Open the Store (
Store.open) unless a Store was injected. The runtime pool opens without waiting so a cold Lakebase connect does not block startup. - Run pending schema migrations (
Store.initialize) in a worker thread, then close the dedicated migration pool. - Initialize MLflow tracing (
_configure_tracing) and store the experiment ID onapp.state. A local profile that cannot reach its tracking server, or any initialization error, raisesRuntimeErrorand aborts startup. - Build the
SessionRuntimeRegistry. - On shutdown, close the registry (sandboxes then leases), close the Store, and
flush MLflow's async trace logging. The flush is suppressed on error because
uvicorn re-raises
SIGTERMand would otherwise skip MLflow's exit-time flush.
Route dependencies live in src/fleet_rlm/api/deps.py. get_settings returns
app.state.settings, get_store and get_registry answer 503 when the
corresponding resource is missing, and get_runtime resolves the path's Session
ID through the registry, translating a bad ID into 422.
| Module | Prefix area | Router |
|---|---|---|
src/fleet_rlm/api/routes/sessions.py |
Sessions, models, mode, history, Turn status, context | sessions.router |
src/fleet_rlm/api/routes/sessions.py |
Memory | sessions.memory_router |
src/fleet_rlm/api/routes/sessions.py |
Skills | sessions.skills_router |
src/fleet_rlm/api/routes/workspace.py |
Workspace and attachments | workspace.router |
src/fleet_rlm/api/routes/turns.py |
Turn stream | turns.router |
All routers are included without a global prefix, so paths are absolute. The
route inventory is in Endpoints and the generated contract is
openapi.yaml, checked with make api-check.
All handlers except the Turn stream are plain def functions. FastAPI runs them
in its bounded threadpool, which is what makes blocking calls (psycopg, Daytona
volume access, model discovery) safe without turning them async. Only
POST /sessions/{session_id}/turn is async def; it multiplexes a queue and
runs the Turn itself on a worker thread.
Request and response bodies are Pydantic models in
src/fleet_rlm/api/schemas.py. Every request model inherits _Request, which
sets extra="forbid", so a misspelled option is a 422 instead of being silently
ignored. FleetTask text is capped at MAX_TASK_CHARS; workspace writes and
uploads are capped at 25 MB.
Errors are translated at the route boundary by translate in
src/fleet_rlm/api/deps.py:
- a
ValueErrorbecomes 422 (invalidmessage or the exception text); - any other exception becomes the route's sanitized 503 message, logged by type only;
- an
HTTPExceptionraised inside the block passes through unchanged.
The translate helper is also used with status=404 for workspace reads, stat,
and delete, where an invalid path and a missing file are the same client answer.
GET /health is a synchronous route that reads app.state. It returns
status, version (package metadata for fleet-rlm), and an mlflow object
with the non-secret destination: mode, tracking URI, workspace host, experiment
names, trace location, resolved experiment ID, and ready.
The health payload contains no credentials. fleet cli and evaluation preflight
compare this destination before reusing an already-running server
(src/fleet_rlm/launcher.py). A server pointed at a different MLflow experiment
or workspace is rejected rather than silently reused.
All Session-scoped state changes and reads that touch the sandbox take
SessionRuntime.turn_lock through session_lock. The wait is two seconds; a
Turn can hold the lock for the whole Turn timeout, and queueing requests on a
bounded threadpool would pin workers. When the wait expires the route answers
409 with a retry message. Memory, Skill, context, mode, model, workspace, and
attach routes declare this response in their OpenAPI entry through LOCKED.
The lock also fences a stale worker: after acquiring it, session_lock calls
assert_lease_held() and answers 503 if this process no longer owns the
Session's PostgreSQL advisory lease.
Turn status and history deliberately do not take the lock. They are durable PostgreSQL reads that stay available while another worker owns the runtime.
graph TD
Client["TUI or HTTP client"] --> App["FastAPI app (api/app.py)"]
App --> Deps["deps.py<br/>settings, Store, registry, runtime"]
Deps --> Store["Store<br/>PostgreSQL"]
Deps --> Registry["SessionRuntimeRegistry<br/>advisory leases"]
Registry --> Runtime["SessionRuntime"]
Runtime --> Engine["AdaptiveFleetEngine"]
Runtime --> Sandbox["DaytonaSandboxManager"]
App --> Health["GET /health<br/>version + MLflow destination"]
App --> Turns["POST /sessions/{id}/turn<br/>SSE"]
- The terminal client consumes the same routes documented here; see
src/fleet_rlm/tui/app.py. - The Store schema and migration ledger are described in Store schema and migrations.
- Turn orchestration inside the worker thread is described in Turn pipeline.
- Event envelope construction is described in SSE streaming.
- New route area: add a module under
src/fleet_rlm/api/routes/, include its router increate_app, and regenerateopenapi.yamlwithmake api-sync. - New request/response shape: add a model to
src/fleet_rlm/api/schemas.py; request models must inherit_Request. - New dependency or error translation rule:
src/fleet_rlm/api/deps.py. - New lifecycle resource: the
lifespancontext insrc/fleet_rlm/api/app.py, with cleanup in the reverse order of acquisition.
| File | Role |
|---|---|
src/fleet_rlm/api/app.py |
create_app, lifespan, router inclusion, /health, MLflow startup check |
src/fleet_rlm/api/deps.py |
Dependencies, translate, session_lock, 409 constant |
src/fleet_rlm/api/schemas.py |
Pydantic request and response models |
src/fleet_rlm/api/events.py |
DSPy callback and SSE envelope |
src/fleet_rlm/api/routes/sessions.py |
Session, model, mode, history, context, memory, Skills routes |
src/fleet_rlm/api/routes/workspace.py |
File listing, stat, read, write, delete, attach |
src/fleet_rlm/api/routes/turns.py |
Streaming Turn handler |
openapi.yaml |
Generated API contract |
docs/reference/http-api.md |
User-facing route reference |