| sidebar_position | 4 |
|---|---|
| title | Architecture |
| description | Understand how the web app, SDK, API, AI service, worker, and infrastructure fit together before making cross-cutting changes. |
Move37 is not a single process. It is a small system with a few important boundaries:
- the main FastAPI API owns auth, graph mutations, notes CRUD, calendar sync, chat session persistence, and MCP transport
- the internal AI service owns semantic note search and grounded answer generation
- the notes worker owns embedding jobs and keeps Milvus in sync with notes stored in Postgres
- the web app and Node SDK both talk to the main API, not directly to the AI service
- MCP tools are another transport layer over the same service logic already used by REST where REST exists
src/move37/api builds the FastAPI app and exposes:
- REST routes under
/v1 - MCP routes under
/v1/mcp/* - a simple
/healthendpoint
src/move37/services/container.py wires the main runtime:
- SQLAlchemy session factory
AppleCalendarSyncServiceActivityGraphServiceSchedulingServiceNoteServiceChatSessionServiceMove37AiClient
That last point matters: the main API does not answer note search or chat itself. It forwards those requests to the internal AI service over HTTP.
REST routes currently cover six broad areas:
- auth
- graph
- notes
- calendar
- integrations
- scheduling
The calendar-related REST surface is split in two:
/calendars/apple/*covers event listing and reconciliation/integrations/apple/*covers account connection, status, and preferences
MCP routes are exposed separately, and chat now lives there rather than in the browser or REST surface. If you change a service contract or persistence rule, think about both transports.
There are three separate flows to understand:
- A user creates or imports a note through the main API.
- The note is stored in Postgres.
- A linked graph node is created or updated.
- A note-embedding job is queued.
This flow works even if the notes worker has not processed the job yet.
- The client calls
/v1/notes/searchon the main API. - The main API forwards that request to
move37-ai. move37-aiembeds the query and searches Milvus.- Matching note chunks are returned to the main API response.
This path depends on:
move37-ai- Milvus
- embeddings already having been written by the worker
OPENAI_API_KEY
- An MCP client creates or loads a chat session through the main API.
- The MCP client posts a chat message to the main API.
- The main API forwards the message to
move37-ai. move37-airetrieves relevant note chunks from Milvus and runs the LangGraph answer flow.- The assistant response is persisted back in Postgres.
Move37 currently spreads state across several systems:
- Postgres for the main application state
- Milvus for vector search over note chunks
- Langfuse storage for tracing when enabled
- Prometheus and Loki for observability data when the local observability stack is running
That means a feature can “work” in one layer and still fail end to end if another service is missing.
src/move37/web is a React and Vite app served by nginx in the web container. The nginx config proxies /v1 and /health back to the API container.
src/move37/sdk/node is the current hand-written JavaScript SDK. It mirrors the REST API and also ships React hooks that the web app consumes.
The web app is therefore a good reference for how the SDK is expected to feel, while the SDK is a good reference for which API routes are already treated as part of the client surface. Chat is the exception: it is supported through MCP clients rather than the web app or REST SDK.
Move37 intentionally keeps internal and external docs separate:
contributing-docs/: internal contributor and repository-operating documentationfern/: external developer-facing API, SDK, and CLI documentation
Do not mix those audiences. If a page explains how to run the repo, ship code, or understand CI and deployment, it belongs here. If it teaches someone how to call Move37 as a product or integration surface, it belongs in fern.