Skip to main content

Architecture

Stack​

ConcernChoice
Web frameworkFastAPI on Uvicorn, one process
RenderingServer-side HTML built in Python. No template engine, no bundler.
DatabaseSQLite, one file, accessed through the standard library. No ORM. Schema and migrations are hand-written in matilda/db.py.
AgentStrands Agents SDK with the OpenAI-compatible client pointed at Kimi (Moonshot AI)
AuthApp-owned email and password, PBKDF2-HMAC-SHA256 at 200,000 rounds, HMAC-signed session cookie
SchedulerAPScheduler background job on a 15-minute interval for reminders
PDFWeasyPrint, which needs Pango
PackagingDocker image on python:3.14-slim, published to GitHub Container Registry
DesignLiquid Amethyst, all tokens and components inlined from matilda/ui.py

Modules​

matilda/
├── server.py # FastAPI app: routers, /health, /webhook
├── agent.py · prompts.py # Strands agent, turn pipeline, system prompt
├── ui.py # Design system: tokens, glass, orb, sidebar shell
├── dashboard.py # Overview, leads, tasks, setup, prompt, org, devices, PMS
├── dashboard_auth.py # Email + password login, sessions, role guard
├── customer_auth.py # Web-chat phone + email OTP
├── worker_api.py · worker_auth.py # Worker link/ingest and per-device keys
├── followupboss.py # Follow Up Boss client and capture-time sync
├── netguard.py # Optional CIDR allowlist under the dashboard
├── db.py · analytics.py # SQLite layer and read-only dashboard queries
├── qualification.py # Milestones, scoring, promotion, handoff trigger
├── delivery.py · memory.py # Reply and reaction delivery; context and extraction
├── lead_page.py · mailer.py# Public lead sheet + PDF; Resend for codes and handoff
├── landing.py · chat.py # Landing page; web chat UI and API
├── roles.py # The single role policy table
├── scheduler.py # Reminder job
├── pms.py # Curated fixtures for property-management surfaces
├── seed.py # Sample lead generator
└── tools/ # Agent tools: send/react, contacts, reminders, leads

Request flow for an inbound text​

  1. BlueBubbles posts a new-message event to /webhook?token=....
  2. The server checks the token, de-duplicates on the message identifier, and hands off to the agent.
  3. The agent loads the contact, builds the known contact block, and runs one turn with the Kimi reply model.
  4. The model calls tools: at most one send_imessage and optionally one react_to_message. Replies go back through BlueBubbles.
  5. After the turn, the extraction model reads the exchange and updates the contact's profile and memory.
  6. Qualification is re-scored. If the lead just crossed into qualified, the handoff fires.

Web chat follows the same path with /api/chat as the entry point and the reply returned in the response body instead of sent through BlueBubbles.

Worker ingest flow​

  1. The Matilda Worker on a Mac posts tagged threads to /api/worker/ingest with a bearer key.
  2. Messages are stored with source personal_imessage, de-duplicated by GUID.
  3. Extraction runs. Lead events are recorded for each captured field.
  4. The person is created or enriched in the Mac's CRM. With "suggestions": true (current workers), notes, tasks, appointments, the stage move, and fitting drip campaigns become suggestions (matilda/suggestions.py) that reach the CRM only when the operator applies one. Without it, as older workers send, notes, tasks, appointments, and the stage are pushed. Nothing is auto-replied.

Configuration precedence​

Settings are read through accessors that check the settings table first and fall back to the environment. Anything saved on the dashboard Setup page therefore wins over .env with no restart.

Storage layout​

All state lives under MATILDA_DATA_DIR, by default ./data:

  • matilda.db is the SQLite database.
  • sessions/ holds agent session state.

Back up the directory to back up Matilda.