The CRM: Every Person Becomes an Entity
Before this, read:
- Files as state, not chat memory — entity files are just files; understanding why matters
- Mnemosyne: the hybrid memory layer — CRM entities become graph nodes
Every contact management system starts with the same good intention — write down who you know — and ends the same way: half-filled, never updated, discovered only when you’re trying to remember someone’s last name before a call.
The JD system took a different approach. The CRM is not something you go to. It’s something that happens to every person mentioned, automatically, whether you thought about it or not.
The rule
Section titled “The rule”From ~/agent-system/CLAUDE.md:
CRM Everything: whenever a person’s name comes up, check/create their entry at
~/clawd/memory/entities/people/<slug>.mdwith contact info and interaction log. This applies to ALL names — classmates, professors, contacts mentioned in emails, calendar invites, Slack, etc. When in doubt, create the entry.
The rule is not a preference. It’s a session-level requirement. A PreToolUse hook (crm-name-lookup.py) runs on every UserPromptSubmit event and scans the incoming message for person signals — honorifics, interaction verbs, email addresses, phone numbers, @mentions. When it finds one, it looks up or creates the CRM entry before the agent responds.
The auto-CRM utility
Section titled “The auto-CRM utility”The command:
python3 -m agents.shared.crm "Full Name" \ --context "how you met" \ --domain school \ --role "Title"This creates or updates ~/clawd/memory/entities/people/<slugified-name>.md with:
- Identity block: name, role, domain, how they’re connected to JD
- Contact info: email, phone, LinkedIn (added as found)
- Interaction log: timestamped entries, one per encounter — emails, calls, meetings, Telegram messages
The file format is markdown with YAML frontmatter. The frontmatter is what Mnemosyne reads at ingest time to build the Kùzu graph edges (KNOWS, WORKS_AT, INTERACTED_ON). The free-text body is what the vector embedding captures.
The garbage-stub problem
Section titled “The garbage-stub problem”By June 2026, the CRM had grown to 756 people files. Then an audit found the problem: 176 of them were garbage. The hook that created entries was too loose — it was picking up prompt-fragment names ("Claude", "Assistant"), Plaud sentence fragments ("Executive Assistant"), file paths that looked like names, and code snippets that happened to contain capitalized words.
The fix was root-cause treatment, not a manual deletion pass:
- The hook was rewritten (
crm-name-lookup.py) to require a genuine person signal before creating an entry — not just a capitalized noun, but an honorific, interaction verb, email, phone, or explicit@mention. - 186 ambiguous stubs were audited and classified: 155 quarantined to
people/.quarantine/, 31 kept (they were real people with minimal info). - A weekly regression guard (
cron-crm-garbage-audit.sh, Sunday 8:40am) runs the same detection logic and Telegram-pings JD if new garbage appears. The fix to the process doesn’t just clean up the backlog — it prevents the backlog from recurring.
After the cleanup: 475 people files in active use. Those 475 are the seed dataset for Mnemosyne’s crm_people collection.
The snapshot sync
Section titled “The snapshot sync”A cron job runs every 30 minutes:
*/30 * * * * python3.12 -m agents.ai_os.crm_syncThis syncs the crm_contacts Supabase table from the people/ directory. The nerve-center cockpit reads from Supabase, so the CRM pages in the UI stay current with a 30-minute lag. Changes made by agents (phone numbers found in emails, meeting notes added after a call) flow to the UI without any manual step.
What an entity file looks like
Section titled “What an entity file looks like”A real example structure (scrubbed of personal details):
---name: Felix Vivancoslug: felix-vivancorole: Mortgage Brokerdomain: consultingcompany: Vivanco Mortgageemail: felix@vivancomortgage.comadded: 2026-06-07---
## How we connectedMet through JD's consulting pipeline (Slopes Mortgage project). Felix is the principal broker.
## Key contextRunning a mortgage CRM project (Chase Clement leading dev). Shared Dropbox folder for the Subefy app (Spanish credit-repair, shared 2026-06-07 23:21).
## Interaction log- 2026-06-07 — Joined the Slopes Mortgage roadmap call (145 min); roadmap extracted at consulting/state/slopes-mortgage-roadmap-2026-06-08.md- 2026-06-08 — Dropbox folder reviewed (Subefy). Business email confirmed.The interaction log is append-only. Agents add entries; they do not edit earlier ones. That append-only pattern is the same as the master CHANGELOG — a truthful record of what happened, in order.
The sources that feed the CRM
Section titled “The sources that feed the CRM”The CRM is populated by four concurrent paths:
- The session hook — names in Telegram messages, during active agent sessions
- Plaud transcripts — voice notes transcribed and routed through
plaud_handlers.py; the CRM enricher adds entities from meeting notes - Calendar attendees — a daily cron (
agents.ai_os.calendar_crm) adds anyone who appears in calendar invites - iMessage contacts — a metadata-only ingest (not message content) that backfills phone numbers to existing entries
The Plaud path had a precision problem: the original name-extraction used a regex that invented entries from sentence fragments. The LLM-validated extraction (agents.ai_os.memory.py’s LLM gate) was added specifically to fix this — every candidate name goes through a validation step before an entry is created. The garbage-stub audit that found 176 bad entries traced back to the earlier regex path.
Why “when in doubt, create the entry”
Section titled “Why “when in doubt, create the entry””The asymmetry favors over-capture. A missing entry means you’re searching for someone you’ve interacted with and finding nothing. A spurious entry sits dormant until the garbage audit sweeps it. The cost of a false positive in the CRM is a cron job finding it and parking it. The cost of a false negative is an agent making decisions without knowing that JD already has a relationship with the person.
The CRM is the lowest-friction trust signal the system has. When the executive assistant drafts a follow-up email and checks whether JD has history with the recipient, the CRM is the first and cheapest lookup. If it’s empty, the agent has no choice but to treat the person as a stranger.
That’s the real cost of an under-populated CRM — not a gap in a database, but an agent that treats your long-time professor like a cold call.
Next: PCA: the Personal Context Artifacts — the four files that any decision-making agent loads to understand who JD is and how he decides.