This repo contains the logic, templates, scripts, and skill instructions for a private AI-first personal CRM built on a markdown/git vault.
The system is optimized for relationship-led advisory work, while still supporting consulting, dealmaking, and founder/investor matchmaking workflows.
The source of truth is not this repo. The source of truth is the private vault at CRM_DATA_PATH, typically ./crm-data/.
This repo gives an agent or developer the machinery to:
- create and maintain CRM records in markdown
- sync Gmail and Google Calendar into relationship memory
- enrich that ingest with meeting-note discovery from Google Drive plus optional post-ingest Granola and WhatsApp review
- mirror CRM tasks into Google Tasks with durable task IDs
- generate a relationship-first dashboard
- track leads before conversion
- process raw capture through an
Inbox/ - assemble relationship memory from
Notes,Activities,Tasks, and linked entities - suggest investor/deal matches
The current canonical schema lives in docs/schema-spec.md.
apps/api/: standalone read-only FastAPI API for programmatic consumers building their own CRM clients.apps/web/: local FastAPI HTML application for the read-only dashboard MVP.apps/cli/: reserved application package for future first-class CLI UX.scripts/: current operational utilities and lower-level CRM workflow helpers.- Web and future CLI surfaces may call the API service layer where appropriate, but web-specific routes/templates/static assets should stay out of
apps/api/and CLI command code should stay out ofapps/web/.
If you are a new agent or developer and need to get productive quickly, read in this order:
docs/schema-spec.mdAGENTS.mdcrm-data/DASHBOARD.mdcrm-data/index.mdcrm-data/log.md
Then adopt these current assumptions:
crm-data/is the system of recordcrm-daily-processingis the preferred top-level daily workflowDeal-Flow/is still the live deal inventory directorywaitingis a first-class task state and itsdue-dateshould usually mean the next review/check-back date- a company may exist both as a
Dealand as anOpportunity, but those records mean different things - Workspace ingest now means: Gmail / Calendar first, then additive Drive, optional Granola, and optional WhatsApp post-passes
Use this distinction:
Deal: a company / startup in inventory that can be matched to investorsOpportunity: the pre-close commercial pursuitEngagement: the post-close commercial and execution containerWorkstream: a specific execution lane inside an engagement
Think in two layers:
crm-llm/: public-friendly logic layercrm-data/: private vault and operational system of record
The vault is where the real work happens. The logic layer exists to read, write, enrich, and synthesize that vault.
The vault root now maintains two navigation files for agents and operators:
index.md: a generated, content-oriented catalog of every CRM record grouped by entity typelog.md: an append-only chronological ledger of mutation workflows such as creates, conversions, inbox processing, and ingest runs
Operating rules:
- read
index.mdfirst when locating relevant CRM pages - treat
index.mdas derived state and rebuildable - treat
log.mdas append-only operational history - mutation workflows should update both automatically
The system currently uses this memory model. The key record types are:
Organization: stable market entityInbox: temporary raw captureLead: pre-conversion relationship recordContact: person recordAccount: commercial relationship recordOpportunity: pre-close commercial pathEngagement: post-close commercially won workWorkstream: execution lane within an engagementDeal: startup/inventory seeking capitalSource Artifact: canonical pointer to external evidence or working documentsRetainer: recurring commercial commitment under an engagementInvoice: billed obligation tied to an engagementPayment: received payment tied to an invoiceNote: durable context and strategic memoryActivity: real interaction or eventTask: explicit next action
Important current rules:
Inbox/replaces the old “notes as inbox” pattern.Notes/are durable context, not raw intake.- If something happened, it should usually create an
Activity. Leadis first-class and converts by default toOrganization + Contact + Account + Opportunity.Organizationowns stable identity/classification, whileAccountowns the active commercial relationship.- Investor mandate and check-size stay on the organization side; fundraising stage and target raise stay on
Deal. commercial-valueis canonical on Opportunities;deal-valueis compatibility-only.- closed-won opportunities should usually hand off into an
Engagement. - every
Workstreammust belong to exactly oneEngagement. - The default home view is relationship-first, not chronology-first.
DealandOpportunityare intentionally separate:- use
Dealfor investor-shop inventory - use
Opportunityfor John's possible mandate with that company
- use
- Python 3
- Gemini CLI or another compatible coding agent runner
gwsCLI authenticated to the relevant Google Workspace account- local
codexCLI if you want the optional Granola post-ingest pass to run automatically - local
wacliif you want the optional WhatsApp post-ingest pass to run automatically
Create .env in the repo root:
CRM_DATA_PATH=./crm-data
python3 .gemini/skills/init-crm-data/scripts/init-vault.py crm-dataSync Workspace:
CRM_DATA_PATH=./crm-data python3 .gemini/skills/crm-ingest-gws/scripts/ingest.pySync Workspace but skip the Granola post-pass:
CRM_DATA_PATH=./crm-data python3 .gemini/skills/crm-ingest-gws/scripts/ingest.py --skip-granolaSync Workspace but skip the WhatsApp post-pass:
CRM_DATA_PATH=./crm-data python3 .gemini/skills/crm-ingest-gws/scripts/ingest.py --skip-whatsappRefresh dashboard and derived views:
CRM_DATA_PATH=./crm-data python3 .gemini/skills/update-dashboard/scripts/update-dashboard.py --skip-followups --skip-commitCreate a lead:
python3 .gemini/skills/crm-lead-manager/scripts/lead_manager.py create --name "Example Lead" --status newProcess an Inbox item:
python3 scripts/inbox_manager.py process <item> --outputs note activity task --primary-parent-type opportunity --primary-parent "Opportunities/Example"Create an Activity directly:
python3 scripts/record_manager.py create-activity --title "Intro call with Jane" --activity-type meeting --date 2026-03-15 --primary-parent-type opportunity --primary-parent "Opportunities/Example"Create or review an Opportunity workflow:
python3 scripts/opportunity_manager.py review "Opportunities/Example"Mark an opportunity won and create the post-close handoff:
python3 scripts/opportunity_manager.py mark-won "Opportunities/Example" --create-engagement --engagement-type advisory --commercial-model retainer --create-workstream --workstream-type researchReview or add post-close execution structure directly:
python3 scripts/engagement_manager.py review "Engagements/Example"
python3 scripts/engagement_manager.py create-workstream --engagement "Engagements/Example" --workstream-type research --name "Market Validation Track"Create finance records for active delivery:
python3 scripts/finance_manager.py create-retainer --engagement "Engagements/Example" --amount 5000 --currency USD --cadence monthly
python3 scripts/finance_manager.py create-invoice --engagement "Engagements/Example" --workstream "Workstreams/Example" --amount 2500 --currency USD --due-date 2026-07-15Create a source artifact tied to a workstream or engagement:
python3 scripts/source_artifact_manager.py create --primary-parent-type workstream --primary-parent "Workstreams/Example" --source-system google-drive --source-type doc --url "https://docs.google.com/..."Rebuild the CRM index manually:
python3 scripts/navigation_manager.py rebuild-indexCreate an Account linked to an Organization:
python3 scripts/account_manager.py create --organization "Organizations/Example-Capital" --relationship-stage prospect --strategic-importance mediumCreate a Contact linked to an Account:
python3 scripts/contact_manager.py create --name "Jane Doe" --account "Accounts/Example-Capital" --email "jane@example.com"Create a Deal in the live Deal-Flow/ inventory:
python3 scripts/deal_manager.py create --name "Example Startup" --fundraising-stage "Series A" --coverage-status activeCreate or update a Task with the live status model:
python3 scripts/task_manager.py create --name "Follow up with Jane" --due-date 2026-04-10 --primary-parent-type opportunity --primary-parent "Opportunities/Example"
python3 scripts/task_manager.py set-status "Tasks/2026/04/2026-04-10-follow-up-with-jane.md" --status waiting --review-date 2026-04-13
python3 scripts/task_manager.py set-status "Tasks/2026/04/2026-04-10-follow-up-with-jane.md" --status completedSync CRM tasks to Google Tasks:
python3 .gemini/skills/crm-sync-google-tasks/scripts/sync-tasks.pyRun the local CRM web dashboard:
uv sync
CRM_DATA_PATH=./crm-data uv run uvicorn apps.web.crm_frontend.app:app --host 127.0.0.1 --port 8000 --reloadThen open http://127.0.0.1:8000.
The web dashboard lives under apps/web/ and is read-only in the MVP. It live-reads Leads/, Opportunities/, and linked CRM context from CRM_DATA_PATH; it does not mutate markdown records, index.md, or log.md.
Run the standalone read-only CRM API:
CRM_DATA_PATH=./crm-data uv run uvicorn apps.api.crm_api.app:app --host 127.0.0.1 --port 8001 --reloadThe API exposes JSON endpoints such as /pipeline and /records/{item_key} for custom clients.
For a new operator or agent, the normal loop is now best treated as the top-level skill crm-daily-processing.
That workflow should:
- run Workspace ingest
- review staged suggestions
- ask the user for off-system updates from WhatsApp, in-person meetings, calls, and other uncaptured channels
- reconcile
todo,waiting, and stale tasks - review live opportunities, engagements, workstreams, and leads
- refresh the dashboard and derived views
At a lower level, the manual sequence is:
- Run Workspace sync.
- Review
crm-data/staging/activity_updates.json. - Review
crm-data/staging/contact_discoveries.jsonandcrm-data/staging/lead_decisions.json. - Review
crm-data/staging/opportunity_suggestions.jsonandcrm-data/staging/task_suggestions.json. - Review
crm-data/staging/drive_document_updates.jsonandcrm-data/staging/granola_updates.jsonwhen present. - Process or create
Inbox/items into durable records. - Create or update
Leads,Activities,Notes,Tasks,Engagements,Workstreams, and finance records as needed. - Run the dashboard refresh.
If you only do one thing to get oriented in a live vault, read:
crm-data/DASHBOARD.mdcrm-data/INTELLIGENCE.mdcrm-data/RELATIONSHIP_MEMORY.mdcrm-data/index.md
Workspace sync now has two important guarantees:
- it always tries to read the underlying Gmail body or Calendar event details before creating a durable Activity
- it persists a sync checkpoint in
crm-data/staging/workspace_sync_state.json
That means:
- repeated sync runs resume from the last successful Gmail/Calendar checkpoint by default
source-refis still used as a second dedupe layer- passing
--since YYYY-MM-DDoverrides the saved checkpoint for backfills
Current ingest behavior is broader than just Gmail / Calendar:
- Gmail messages are processed with thread-aware task detection and can use prior thread context plus attachment metadata
- calendar and email events still do broad meeting-note lookup first
- a later additive Drive pass can ingest CRM-labeled Google Docs that were updated since the sync window
- a later optional Granola pass can create deduped Activities and Tasks from recent meetings if local Codex + Granola MCP are configured
- a later optional WhatsApp pass can stage CRM-relevant chat signals from local
waclihistory if enabled in settings
Codex desktop operational note:
gwsneeds live Google API network access; restricted sandbox runs can fail before Gmail / Calendar ingest starts with DNS or host-resolution errors.- For the recurring CRM daily automation, use the approved unrestricted command form:
/bin/zsh -lc 'set -a; [ -f .env ] && . ./.env; set +a; CRM_DATA_PATH=${CRM_DATA_PATH:-./crm-data} python3 .gemini/skills/crm-ingest-gws/scripts/ingest.py --autonomous --auto-tier 1'- If a sandboxed attempt hits a Google API network error, rerun that command with escalation before treating Workspace ingest as blocked.
Current staging / audit files you should expect from ingest:
crm-data/staging/activity_updates.jsoncrm-data/staging/contact_discoveries.jsoncrm-data/staging/lead_decisions.jsoncrm-data/staging/opportunity_suggestions.jsoncrm-data/staging/task_suggestions.jsoncrm-data/staging/drive_document_updates.jsoncrm-data/staging/granola_updates.jsoncrm-data/staging/whatsapp_updates.jsoncrm-data/staging/ingestion_audit.jsoncrm-data/staging/workspace_sync_state.json
Useful ingest settings in crm-data/settings.json:
crm_drive_label_idsgranola_post_ingest_enabledgranola_post_ingest_lookback_dayswhatsapp_post_ingest_enabledwhatsapp_post_ingest_lookback_dayswhatsapp_accountwhatsapp_store_dir
Granola is not required for baseline CRM operation. If you want the automatic post-ingest Granola pass, configure Granola MCP in local Codex first:
codex mcp add granola --url https://mcp.granola.ai/mcp
codex mcp login granola
codex mcp listPractical notes:
- the current implementation calls Granola through local
codex exec - if Codex or Granola MCP is unavailable, ingest should still complete normally
- Granola-derived records preserve durable provenance in
source/source-refso future runs can dedupe correctly
WhatsApp is not required for baseline CRM operation. If you want the automatic post-ingest WhatsApp pass:
- install and authenticate
wacli - enable it in
crm-data/settings.jsonwithwhatsapp_post_ingest_enabled - optionally set:
whatsapp_post_ingest_lookback_dayswhatsapp_sync_max_db_size(default500MB; safety ceiling for the SQLite archive)whatsapp_archive_max_messages(default5000; retains the newest X messages after CRM processing, or0to disable pruning)whatsapp_accountwhatsapp_store_dir
Practical notes:
- the current implementation first runs a bounded
wacli sync --once, then reads the local SQLite archive in read-only mode - sync failures caused by missing authentication, connectivity, or a held store lock are recorded in
whatsapp_updates.json; the pass fails open and reads any existing archive it can access - on the first WhatsApp run without a saved checkpoint, ingest scans the full local
waclihistory instead of only a short recent window - after checkpointing begins, the SQLite rowid is the durable cursor so messages added late after a sync outage are not excluded by older WhatsApp timestamps
- after CRM processing succeeds, rolling retention keeps only the newest
whatsapp_archive_max_messagesrows by WhatsApp timestamp; pruning results are recorded in staging - if
wacliis unavailable or its store cannot be read, ingest should still complete normally - WhatsApp results are staged in
crm-data/staging/whatsapp_updates.json - the current policy is intentionally conservative: unanchored WhatsApp groups are ignored by default, and unanchored direct chats require strong business signal before staging anything
- for direct chats, ingest now also allows conservative exact-name soft matching against a unique CRM Contact or Lead and uses recent thread context to interpret short acknowledgements
Use YYYY-MM-DD dates everywhere.
Current filename conventions:
- all CRM record filenames use hyphen-separated slugs; do not use spaces or punctuation in record filenames
- new
Activities:YYYY-MM-DD-<slug>.md - new generated
Tasks:YYYY-MM-DD-<slug>.md Activities/,Tasks/, andNotes/are bucketed underYYYY/MM/
Legacy files may still exist in older filename shapes. Do not assume the whole vault is perfectly migrated.
- Always resolve
CRM_DATA_PATHdynamically from.envor the environment. - Always use the templates in
templates/for new records. - Treat
index.mdas generated state; rebuild it rather than editing it manually. - Treat
log.mdas append-only; do not rewrite old entries. - All wikilinks in YAML frontmatter must be quoted, for example:
account: "[[Example Account]]"
- Prefer updating existing linked records over creating duplicates.
- For Gmail and Calendar ingestion, do not create meaningful records from subject lines alone.
crm-datamay be a nested git repo or ignored locally. Check before assuming normal git behavior.
Current execution rules:
todo: you owe the next movewaiting: someone else owes the next movecompleted: done or clearly superseded- when moving a task to
waiting, updatedue-dateto the next review date - task records may also persist
google-task-idandgoogle-task-list-idwhen linked to Google Tasks - for company fundraising work, decide explicitly whether you are recording a
Deal, anOpportunity, or both
Google Tasks operating rule:
- the CRM remains the source of truth for task creation and structure
crm-sync-google-tasksmirrors CRM-created tasks into Google Tasks and may pull remote completion back into the CRM- do not assume Google-native personal tasks belong in the CRM unless an explicit intake workflow is added later
Preferred current write surface:
- use the manager CLIs for
Organization,Account,Contact,Deal,Task,Lead,Opportunity,Engagement, finance, and source-artifact lifecycle work - use
record_manager.pyfor first-classActivityandNotecreation - use
inbox_manager.pyfor raw capture processing
The most relevant skills for real use are:
crm-daily-processingcrm-ingest-gwscrm-sync-google-tasksupdate-dashboardcrm-lead-managercrm-opportunity-managercrm-engagement-managercrm-finance-managercrm-source-artifact-managercrm-create-accountcrm-create-contactcrm-create-dealcrm-create-daily-reportcrm-create-organizationcrm-create-engagementcrm-create-leadcrm-create-inbox-itemcrm-create-invoicecrm-create-notecrm-create-activitycrm-create-paymentcrm-create-retainercrm-create-source-artifactcrm-create-taskcrm-create-workstreammatchmakermanage-intelligence
Skill definitions live in .gemini/skills/*/SKILL.md.
- ingest.py: Gmail/Calendar ingestion and staged CRM decisioning
- update-dashboard.py: dashboard refresh and downstream generation
- organization_manager.py: organization creation
- account_manager.py: account creation and update
- contact_manager.py: contact creation and update
- deal_manager.py: deal inventory creation and update
- lead_manager.py: lead lifecycle and conversion
- opportunity_manager.py: opportunity lifecycle and execution workflows
- engagement_manager.py: engagement lifecycle and workstream workflows
- finance_manager.py: retainers, invoices, payments, and finance review
- source_artifact_manager.py: source artifact creation, linking, and Readwise import
- task_manager.py: task creation, update, and status management
- sync-tasks.py: CRM-to-Google Tasks sync using persisted Google task identifiers
- inbox_manager.py: Inbox creation and processing
- navigation_manager.py: vault root
index.mdgeneration andlog.mdappends - record_manager.py: first-class Note and Activity creation
- relationship_memory.py: relationship memory assembly
- intelligence-engine.py: telemetry and intelligence generation
- matchmaker.py: deal/account matching
- migrate_accounts_to_organizations.py: reference migration helper
- migrate_opportunities_v41.py: opportunity schema migration helper
For current product and schema context, read:
Historical and superseded design docs live in:
Be aware of these realities:
- the vault contains a mix of current-shape and older records
- the vault still uses
Deal-Flow/as the live deal directory - some legacy Activities and Tasks still use older frontmatter shapes
- some compatibility fields are still tolerated by readers during migration cleanup
- not every workflow auto-commits vault changes
- GitHub MCP auth may be unreliable in this environment;
ghCLI may be the fallback
If you are new to the project, start with the dashboard, inspect one relationship end to end, then run Workspace sync and review staged proposals before making broader changes.
When working inside a live vault:
- Read
index.mdat the vault root to find candidate records. - Drill into the linked pages you actually need.
- Let mutation workflows append to
log.md. - Use
log.mdto understand what changed recently and which workflows have already run.