Local-first personal data mesh: unified semantic search and relationship tracking across email, calendar, iMessage/SMS/RCS, and contacts. Builds vector embeddings via Ollama, maintains identity resolution, and surfaces BEDROCK relationships.
exo-mesh is a STDIO-only personal information management system with 8 tools. Tool definitions are visible in bin/ scripts with docstrings and parameter schemas embedded in Python code. However, critical gaps undermine quality: (1) Naming is inconsistent, tools use prefixes (exo-mail-index, exo-imsg-index) rather than verb_noun patterns; names like 'exo-cal' and 'exo-day' are vague about action intent. (2) Descriptions are domain-specific and lengthy (200-400 chars), optimized for human operators rather than LLM selection, they assume deep context about notmuch, iMessage, and personal mesh databases. (3) Parameter schemas are present but minimal: most lack type constraints, enums, or validation guidance. (4) Output schemas are undocumented, tools return text, JSON, or JSONL but no formal response structure is declared. (5) Error handling is absent, no recovery guidance, no categorization of retryable vs fatal errors. (6) No tool annotations (readOnlyHint, destructiveHint, idempotentHint) despite clear WRITE vs READ_ONLY risk levels. Average per-tool score: 42/100.
Local-first calendar replica (P7). Mirrors the mail stack. Commands: auth (one-time Google consent), pull-google (events.list → calendar.db windowed), pull-apple (Apple Calendar SQLite → calendar.db), pull-all (apple + both google), stats (what's in the replica). Read-only against every source; never writes a calendar.
The unified day surface (P7). One read across the local stores: calendar (calendar.db) + todos (Things SQLite) + mail (exo-mail brief) + journal (a dated markdown note), plus who each of today's attendees is to you, resolved through the relationship mesh (mesh.db). Read-only; composes, never writes. --json for agents.
exo-mail wrapper around lieer: sets the Gmail query lieer has no flag for (Remote.query is a hardcoded class attribute). Default: drop chats, spam, trash and anything over 250K; full history otherwise. Override per-account with EXO_GMI_QUERY.
iMessage/SMS/RCS local replica. Phase 1: snapshot + cursor. READ-ONLY against ~/Library/Messages/chat.db. Never writes to it, ever. Mirrors exo-mail-sync: atomic lock, incremental cursor, JSONL audit, fails loud. Commands: snapshot (WAL-consistent copy + advance cursor + audit), status (cursor, counts, freshness), test (built-in self-checks T1-T4, T10).
Embed iMessage/SMS/RCS day-windows into the shared vector store. Window = (handle, calendar day). One chunk per person per day. Writes into the SAME vectors.db as mail, with account='imessage'. Additive only -- never touches work/personal rows.
Tool names lack action verbs. 'exo-mail-index', 'exo-day', 'exo-cal' do not start with verb_noun patterns (get_, create_, search_, list_). LLMs cannot infer intent from names alone and must parse lengthy descriptions to disambiguate.
No output schemas documented. Tools return text, JSON, or JSONL but response structure is not formally declared. LLMs cannot plan downstream tool calls or extract fields reliably.
No tool annotations (readOnlyHint, destructiveHint, idempotentHint). Risk levels are documented in metadata (WRITE vs READ_ONLY) but not exposed via MCP tool annotations. Agents cannot distinguish safe from destructive operations.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | F | 36 | 2026-07-28+ | v2 |
Exact-phrase (lexical) search over iMessage/SMS/RCS. The dual of notmuch for mail. Day-windows in vectors.db are good at 'what is this relationship about'; they dilute short phrases across a multi-topic day. This is the other half: FTS5 over INDIVIDUAL messages, so a remembered phrase resolves. Commands: --build (rebuild the FTS index from snapshot.db), phrase search, term search with -n limit.
Build/refresh the semantic vector store from the notmuch replica. Local-only embeddings via Ollama (nomic-embed-text, 768d). One sqlite-vec file, both accounts, account column. Chunk -> strip quotes/sig -> embed -> upsert.
Write BEDROCK designations into person files from a confirmed list. Creates person files where none exists (name + emails from the mesh + bedrock:true + relationship + provenance) and updates existing ones by inserting the bedrock frontmatter (never clobbering). Emails/span are read from mesh.db by the confirmed mesh_match name. DRY-RUN by default; --apply writes. Reversible (local markdown).
Parameter schemas lack type constraints and enums. 'command' params accept string enums but no validation guidance. 'limit' and 'since' lack min/max bounds or format specs. LLMs may pass invalid values.
No error handling or recovery guidance. Tools fail silently or return raw errors. No categorization of retryable vs fatal errors. Agents cannot self-correct or plan recovery.