The agent
Matilda is a Strands agent driven by a Kimi model through the OpenAI-compatible API. This page describes the moving parts.
Models
Two model roles, both defaulting to kimi-k2.6:
- Reply model (
KIMI_MODEL) handles the conversation and calls tools. - Extraction model (
KIMI_EXTRACT_MODEL) runs after each turn to update the contact's profile and memory.
Any tool-calling Kimi model works. Replies are capped by KIMI_MAX_TOKENS and truncated gracefully.
System prompt
The prompt in matilda/prompts.py defines Matilda's identity, communication style, reaction policy, the per-turn procedure, the intake flow, and her limits. Operators can read it verbatim on the dashboard at /dashboard/prompt. It cannot be edited from the dashboard.
Tools
| Tool | Purpose |
|---|---|
send_imessage | Send the text reply, via BlueBubbles or the web chat |
react_to_message | Add an iMessage tapback: love, like, dislike, laugh, emphasize, question |
get_contact | Read a contact's profile |
update_contact | Save qualification fields and status |
search_leads | Query the pipeline |
set_reminder | Schedule a follow-up nudge |
get_my_reminders | List a contact's reminders |
cancel_reminder | Cancel one |
Per turn, Matilda sends at most one text reply and optionally one tapback. A tapback alone is acceptable only for pure acknowledgements.
The turn pipeline
- Load the contact and their recent conversation.
- Build the known contact block: name, captured fields, memory, and a still-missing list. This is prepended to the incoming message and treated as the source of truth.
- Run the reply model with the tools.
- Deliver the reply and any reaction.
- Run extraction on the exchange and merge results into the contact.
- Re-score qualification and maybe promote.
Qualification
matilda/qualification.py defines exactly five milestones: name, intent, budget, timeline, pre-approval. The score is the count of captured milestones times 20, so it is always one of 0, 20, 40, 60, 80, or 100.
maybe_promote()flips the status toqualifiedwhen all five are present.fire_handoff_if_newly_qualified()fires the handoff exactly once, on the edge from an earlier status toqualified.
Memory
matilda/memory.py builds the per-turn context and runs extraction. Extracted facts are merged into the memory column as curated long-term notes. The block is rebuilt fresh on every turn, so a fact captured in one message is available in the next.
Handoff
On the first transition to qualified, lead_page.py generates a share token and renders the lead sheet and PDF, and mailer.py sends the notification through Resend if configured.
Scheduler
matilda/scheduler.py runs an APScheduler job every 15 minutes. It looks for reminders that are due, respects each contact's timezone and reminders toggle, and sends the nudge through the same delivery path as a reply.
Sessions
Agent session state is written under MATILDA_SESSIONS_DIR. Deleting a session directory resets the agent's short-term context for that contact without touching the database.