GitHub (public developer hub): https://github.com/8vdx1/fundos-mcp # FundOS by Kela — Agent Guide > This document is the canonical reference for autonomous agents (Claude, > ChatGPT, Cursor, custom MCP clients) integrating with FundOS. Humans > building on this codebase should also read the **Engineering reference** > section below — it preserves the project-internal Claude Code instructions. ## What FundOS is FundOS is an AI-native operating system for fund managers — hedge funds, private equity, private credit, venture capital, and mutual fund managers. It exposes deals, LP investors, capital calls, reconciliation, risk, documents, and CFO functions to AI agents through a unified MCP server so agents can read fund state and propose actions for human approval. ## Connect ```bash claude mcp add fundos https://kela.com/mcp ``` For SSE-only clients: `https://kela.com/mcp/sse`. For Bearer-token API access: `Authorization: Bearer vdr_` against `https://kela.com/api/v1/*`. Key Anthropic-side discovery files: - `https://kela.com/CLAUDE.md` — this document (plaintext) - `https://kela.com/llms.txt` — short FundOS overview for LLMs - `https://kela.com/llms-full.txt` — full developer + integration reference - `https://kela.com/.well-known/mcp.json` — MCP server discovery - `https://kela.com/.well-known/oauth-protected-resource` — OAuth resource metadata - `https://kela.com/.well-known/oauth-authorization-server` — OAuth authorization-server metadata ## MCP tools Every tool below follows the contract: agent can call freely for **reads**; **writes** require human approval (see "DO NOT" list further down). All tools are exposed by `mcp_server.py`; SSE/HTTP transport is at `/mcp/sse` and `/mcp/message`. ### Deal rooms & documents (VDR) | Tool | When to use | |---|---| | `list_deal_rooms` | Picking a room to inspect or act on. | | `get_deal_room` | Loading members + stats for a known room_id. | | `create_deal_room` | Spinning up a new room for diligence or LP onboarding. **HUMAN APPROVAL.** | | `list_documents` | Inventorying a room before searching or summarising. | | `get_document_metadata` | Inspecting a single document's metadata before downloading. | | `get_document_download_url` | Fetching the raw file (PDF/XLSX/DOCX). | | `search_documents` | Answering free-form questions over a room with citations. | | `list_room_members` | Auditing access before granting more or sharing sensitive content. | | `add_room_member` | Onboarding a collaborator or LP to a room. **HUMAN APPROVAL.** | | `list_users` | Matching a person by email or building a team-wide report. | | `get_audit_log` | Investigating suspicious access or building a compliance report. | | `get_document_activity` | Per-document engagement signals — who actually read it. | | `get_signature_status` | Chasing closing signatures across DocuSign envelopes. | | `list_qa_questions` | Triaging the diligence Q&A queue. | | `answer_qa_question` | Posting a reviewed answer to an LP-visible question. **HUMAN APPROVAL.** | ### Deal CRM + Transactions + Pricer | Tool | When to use | |---|---| | `fundos_list_deals` | Reading the deal pipeline. | | `fundos_get_deal` | Loading full detail on one deal before drafting. | | `fundos_create_deal` | Adding a qualified pitch or term sheet to the pipeline. **HUMAN APPROVAL.** | | `fundos_get_pipeline` | Rendering kanban or computing stage counts. | | `fundos_list_transactions` | Reporting on closing pipeline or chasing CPs. | | `fundos_draft_transaction` | Starting a transaction record + default task pack. **HUMAN APPROVAL.** | | `fundos_run_pricer` | Scoring an asset: IRR, MOIC, WAL, cashflows, waterfall splits. | ### Risk | Tool | When to use | |---|---| | `fundos_list_covenants` | Building a portfolio risk dashboard. | | `fundos_check_covenant` | Testing a covenant value when fresh financials arrive. | | `fundos_list_risk_alerts` | Triaging the daily risk inbox. | ### ODD / Diligence / CIM | Tool | When to use | |---|---| | `fundos_generate_odd` | Drafting ODD answers when an LP sends a DDQ. | | `fundos_vdr_analyze` | Surfacing red flags + entity map from a doc bundle. | | `fundos_list_cim_reports` | Listing existing CIMs/teasers/one-pagers. | | `fundos_list_cim_templates` | Discovering available templates before generating. | | `fundos_generate_cim` | Drafting a CIM from source docs for IC or LPs. | ### Investor portal | Tool | When to use | |---|---| | `fundos_list_lps` | Enumerating LPs for rollups. | | `fundos_get_lp` | Loading full LP detail (commitment, KYC, capital-call ledger). | | `fundos_create_capital_call` | Issuing a capital call. **HUMAN APPROVAL — this affects LP cash.** | ### CFO Center | Tool | When to use | |---|---| | `fundos_list_fund_accounts` | Picking a vehicle to compute P&L against. | | `fundos_compute_pnl` | P&L + NAV over a date range. Read-only when called with inline entries. | | `fundos_compute_waterfall` | LP/GP splits at exit (European waterfall). | ### Syndication | Tool | When to use | |---|---| | `fundos_list_syndications` | Reporting raise progress. | | `fundos_get_syndication` | Inspecting a single syndication + allocation matrix. | | `fundos_allocate` | Recording investor allocations. **HUMAN APPROVAL.** | ### HF Ops (DTCC ITP) | Tool | When to use | |---|---| | `hf_ops_dtcc_get_trade_status` | Investigating a settlement break by ctm_ref. | | `hf_ops_dtcc_list_unaffirmed` | Chasing T+0 affirmation. | | `hf_ops_dtcc_get_affirmation_scorecard` | Reporting ops health to COO/PM. | | `hf_ops_dtcc_lookup_ssi` | Fetching a counterparty's SSI when chasing a missing-SSI break. | ### Agent context | Tool | When to use | |---|---| | `fundos_get_agent_context` | **Call this first** at the start of any multi-step workflow. Returns a structured session briefing: recent tool calls, pending human-approval actions, active deals (lookback window), LP roster changes, open risk alerts, upcoming capital calls, recently uploaded documents, and prior agent runs. Supports `lookback_days` (default 30, max 90) and `include_sections` to narrow the payload. Costs 2 credits. Read-only — never modifies state. | ### Generic / catalogue / search | Tool | When to use | |---|---| | `fundos_list_tools` | Discovering the full FundOS @tool catalogue. | | `fundos_call_tool` | Invoking any FundOS tool by name with validated args. | | `search` | ChatGPT Deep Research search adapter — returns `{id, title, url}[]`. | | `fetch` | Reading a Kela document's full text (paired with `search`). | ## DO NOT do without human approval The MCP server exposes some tools that are dual-use. Never execute these without an explicit human OK in the chat / approval queue: 1. **Posting journal entries** — `fundos_compute_pnl` is read-only when given inline entries, but persisting JournalEntries via `fundos_call_tool` (`cfo.post_journal_entry` and similar) requires approval. 2. **Sending LP notices** — capital-call notices, distribution notices, ILPA PDFs/XLSXs that go to LP inboxes. Drafts only; humans click send. 3. **Creating capital calls** (`fundos_create_capital_call`) — affects LP cash. Always draft and route to `/fundos/investors/` for review. 4. **Deleting rooms, documents, or LP records** — irreversible. Use `get_audit_log` to inspect history, never call DELETE endpoints. 5. **Executing trades** — FundOS does not place orders. If a workflow seems to require it, escalate to a human. 6. **Granting access** — `add_room_member`, `create_deal_room`, invite-link issuance. Expanding access to sensitive data is human-only. 7. **OAuth scope escalation** — never request `admin` scope for read tasks. Default to `read`. Use `write` only for explicit data entry. When in doubt: read first, propose second, never persist without a `confirm` field set by a human in the calling chat. ## Webhooks FundOS can push events to your agent's HTTPS endpoint in real time. Webhooks are registered per OAuth client and signed with HMAC-SHA256. ### Register an endpoint ```bash curl -X POST https://kela.com/api/v1/webhooks/ \ -H "Authorization: Bearer vdr_" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-server.example.com/hooks/fundos", "name": "My agent webhook", "event_types": ["credit.low", "action.approval_required", "action.approved"] }' # Returns: { "id": 1, "secret": "abc123...", ... } # Save the secret — it is only returned at creation time. ``` Pass `"event_types": null` to subscribe to all events. ### Verify the signature ```python import hmac, hashlib def verify_fundos_signature(raw_body: bytes, header: str, secret: str) -> bool: expected = "sha256=" + hmac.new( secret.encode(), raw_body, hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected.encode(), header.encode()) # In your endpoint handler: # raw = request.get_data() # sig = request.headers.get("X-FundOS-Signature", "") # if not verify_fundos_signature(raw, sig, MY_SECRET): # abort(401) ``` ### Event catalogue | Event | When it fires | |---|---| | `credit.low` | Balance drops ≤ 100 credits after a tool call | | `credit.exhausted` | Balance reaches 0 (tool call blocked) | | `credit.granted` | Admin tops up credits | | `action.approval_required` | Agent proposes a side-effect (fires from emit_action) | | `action.approved` | GP approves and executes an action | | `action.rejected` | GP rejects an action | | `action.expired` | Pending action auto-expired without a human decision (data: action_id, action_type, expired_at, expiry_hours) | | `job.completed` | AI-heavy tool (analyze/cim/odd) returns successfully | | `job.failed` | AI-heavy tool raises an exception | | `module.enabled` / `module.disabled` | Org admin toggles a module | ### Payload shape ```json { "event": "action.approved", "version": "1.0", "delivery_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-05-01T10:00:00.000Z", "data": { ... } } ``` `delivery_id` is stable across retries — use it to deduplicate. Retries follow: immediate → 1 min → 5 min → 30 min → 2 hr → 8 hr (6 total). ### Management endpoints ``` POST /api/v1/webhooks/ Register GET /api/v1/webhooks/ List PATCH /api/v1/webhooks/ Update (url/name/event_types/is_active) DELETE /api/v1/webhooks/ Deactivate POST /api/v1/webhooks//rotate-secret Rotate signing secret POST /api/v1/webhooks//test Send a ping.test delivery GET /api/v1/webhooks//deliveries List delivery attempts POST /api/v1/webhooks/deliveries//redeliver Re-enqueue a failed delivery ``` ## Common agent workflows ### 1. LP fundraising follow-up 1. `fundos_list_lps` → find target LP by name. 2. `fundos_get_lp` → load commitment + recent capital-call ledger. 3. `search_documents` (room_id from `lp_room_id`) → pull last meeting notes. 4. Draft a follow-up email referencing what was discussed last time. 5. **Stop. Ask the human to send.** ### 2. Diligence on an inbound deck 1. `fundos_create_deal` (with `ephemeral=true`) → validate a draft Deal. 2. **Ask the human to confirm name + counterparty + target_size.** 3. `fundos_create_deal` (`ephemeral=false`) → persist on approval. 4. `create_deal_room` (after human approves) → spin up a diligence room. 5. `add_room_member` → onboard the deal team (humans listed in chat). 6. `fundos_vdr_analyze` once the deck is uploaded → summarise red flags. ### 3. Risk dashboard refresh 1. `fundos_list_covenants` → enumerate everything monitored. 2. For each covenant where new financials are available: `fundos_check_covenant` (with `covenant_id`) → update + alert if breach. 3. `fundos_list_risk_alerts` (`open=true`) → return the unresolved alerts. 4. Render a short summary: "3 breaches, 2 alerts, 12 ok." ### 4. Capital-call ILPA notice 1. `fundos_get_lp` → confirm the LP and pull `lp_room_id`. 2. **Ask the human to draft the CapitalCall row** in `/fundos/investors/`. 3. Once GP marks `ilpa_call_type`, the GP UI's green ILPA button generates the 2025 ILPA Excel notice into the LP's `lp_room_id` → `ILPA Notices/`. 4. Notify the LP only after the human clicks Issue. **Do not auto-send.** ### 5. T+0 affirmation chase (HF Ops) 1. `hf_ops_dtcc_list_unaffirmed` → list confirms still pending. 2. For each: `hf_ops_dtcc_get_trade_status` → fetch live state. 3. If SSI mismatch suspected: `hf_ops_dtcc_lookup_ssi` → confirm. 4. `hf_ops_dtcc_get_affirmation_scorecard` → roll up to a COO summary. 5. **Stop. Send the digest only after a human has reviewed it.** --- # Engineering reference (project-internal) This section is the project-internal Claude Code instructions used by the FundOS engineering team. It is preserved here so that humans editing the codebase have a single source of truth. ## Autonomy & Permissions - Never ask for permission to proceed. Never pause mid-task for clarifying questions — pick the safest/most conservative option and document it at the end. - Never ask "should I continue?" or "would you like me to proceed?" - Only stop if you hit a genuine blocker that only I can resolve (missing API key, business decision). - End every task with a concise summary: what you did, decisions made autonomously, anything I need to do manually. ## Git Workflow - Active branch: **`main`**. Every commit goes there (feat/room-types was merged into main 2026-04-21). - Remote is **`vdx`** (not origin): `git push vdx main`. - Commits auto-deploy to Vercel via GitHub integration (~2 min). Vercel productionBranch = `main`. - Always commit with a clear message before pushing. Use the Co-Authored-By trailer. ## Project Stack | Layer | Technology | |-------|-----------| | Framework | Python Flask | | Database | PostgreSQL (managed) | | File storage | DigitalOcean Spaces (primary) + Vercel Blob fallback — abstracted via `app/utils/storage.py` | | Hosting | Vercel serverless (Python 3.14) | | Production URL | https://www.kela.com (bare kela.com → 308 → www) | | GitHub repo | github.com/8vdx1/FundOS (remote named `vdx`; renamed from `myvdr` 2026-04-28) | | Local dev | `python run.py` → port 5001, or `python -c "from app import create_app; app = create_app(); app.run(port=5002)"` | | AI | Gemini 2.5 Flash via `GEMINI_API_KEY` | ## Product Architecture (as of 2026-04-20) **FundOS is the hero product.** Every authenticated session lands on `/fundos/`, not the old VDR dashboard. The VDR is a module inside FundOS, accessible from the top module bar. ### The 10 FundOS modules | Key | Path | Purpose | |-----|------|---------| | `crm` | `/fundos/crm/` | Deal CRM — pipeline kanban; subnav → "Screen pitch" (Agent B) | | `lp_crm` | `/fundos/lp-crm/` | LP CRM (Fundraising) — meetings, follow-ups, per-LP data room. "Run Autopilot" (Agent A) on index | | `vdr` | `/fundos/vdr/` | VDR + DD Agent — wraps existing `/rooms/`. Each room has "Run Diligence Agent" (Agent C) | | `transactions` | `/fundos/transactions/` | Term sheets, closing coordination. "Extract CPs from term sheet" modal (Agent D) | | `pricer` | `/fundos/pricer/` | Pricing, IRR/MOIC/WAL, waterfall modelling | | `risk` | `/fundos/risk/` | DSRI — covenant monitoring. "Run Risk Agent" (Agent F) on dashboard | | `cim` | `/fundos/cim/` | CIM Builder — per-section Quill editor + Regenerate + Suggest edits | | `investors` | `/fundos/investors/` | Investor Portal — capital calls. Stars icon per row → Agent E reminder ladder | | `cfo` | `/fundos/cfo/` | Fund accounts, P&L, European waterfall | | `syndication` | `/fundos/syndication/` | Allocation matrix, KYC/funds flow | Plus **`team`** at `/fundos/team` — Agent G, multi-agent Deal Team orchestrator. ### Seven Agents (shared AgentRun substrate) All agents write structured `AgentEvent` rows + propose `AgentAction` rows. Nothing auto-executes; the GP clicks Approve on each action at `/fundos/agent/runs/`. Registered action handlers: `lp_crm.log_email`, `lp_crm.add_todo`, `crm.create_deal`, `crm.advance_stage`, `crm.add_note`, `risk.create_alert`, `transactions.add_task`. Every run carries a `trace_id` joined to `CreditLedger` for cost observability at `/admin/usage-traces`. | Agent | Service | Trigger UI | |---|---|---| | A — LP Fundraising Autopilot | `app/services/lp_autopilot.py` | LP CRM index → "Run Autopilot" | | B — Deal Screening | `app/services/deal_screener.py` | CRM subnav → Screen pitch | | C — Diligence | `app/services/diligence_agent.py` | Room page → "Run Diligence Agent" | | D — Closing Coordinator | `app/services/closing_coordinator.py` | Transaction detail → CP modal | | E — CFO reminders | `app/services/cfo_agent.py` | Capital-call row → stars button | | F — Risk | `app/services/risk_agent.py` | Risk dashboard → "Run Risk Agent" | | G — Deal Team orchestrator | `app/services/deal_team.py` | /fundos/team | ### Copilot sidebar (`app/routes/copilot.py`, `static/js/copilot-sidebar.js`) Resizable right-side chat drawer on every FundOS page (loaded from `templates/fundos/_copilot_sidebar.html` in the FundOS base). Drag-to-resize 300–720px (persists `copilot_w` to localStorage), per-user history (`copilot_hist_`), context-aware scope ladder: `lp_id` → LP's bound room, `room_id` → that room, `deal_id` → Transaction.linked_room_id or org-wide, else org-wide cross-room RAG. VI enrichment auto-injects when the page carries a `deal_id` that resolves to a VI-linked Deal. ### Invite Links Shareable workspace URLs at `/admin/invite-links` + `/invite/`. Models: `InviteLink`, `InviteLinkClaim` (unique on `token`). Separate from the older email-bound `Invitation` system. Support `allowed_domain`, `max_uses`, `expires_at`, optional `role_id` preset. Blueprint: `app/routes/invite_links.py`. ### VentureInsights integration Bidirectional. Admin setup at `/admin/integrations/ventureinsights` (`app/routes/vi_integration.py`). **Models** (`app/models.py`): - `VIConnection` — one per org, Fernet-encrypted `bearer_encrypted` (stored as-is, sent as `X-API-KEY` header; NOT Authorization: Bearer), `webhook_secret_encrypted`, three upstream URLs (NestJS / analytics / agent MCP). - `VIImportRun` — per-module import audit + resume cursor. - `VIWebhookEvent` — inbound delivery ledger with UNIQUE(event_id) for idempotency. - External-ref columns: `Deal.vi_company_id`, `LPInvestor.vi_fund_id`, `LPInvestor.vi_investor_id` (all VARCHAR(64), indexed). **Services** (`app/services/vi/`): - `client.py` — `VIClient` facade with `_nestjs_get`, `_analytics_get`, `_mcp_call` routing. - `importers/companies.py`, `importers/funds.py` — shared upsert helpers used by both backfill AND webhook handlers. - `webhooks.py` — `@webhook_handler` decorator + 7 registered handlers (ping.test, company.*, fund.*). - `enrichment.py` — `fetch_vi_context_for_deal`, `fetch_sector_benchmarks`, `fetch_performance_signals`. Used by CIM suggest/regenerate, Copilot, Risk Agent, Deal Team `vi_intel` step. - `reconciler.py` — nightly cron reconciliation. - `outbound.py` — Kela → VI reverse emitter (`odd.profile.updated`, `answer_bank.updated`, `room.red_flag.added`). **Webhook receiver** (`app/routes/vi_webhooks.py`): `POST /webhooks/vi/`, HMAC-SHA256 over `.`, 300s skew window, idempotency via UNIQUE constraint. CSRF-exempt (signature is the auth). `/webhooks/vi/cron/reconcile` at 03:00 UTC daily via Vercel cron. **VI-side patches** (hand-off, push-blocked by 8vdx1 org perms): `docs/p1.5b-8vdx-api-webhooks.patch`, `docs/p4.5-agent-backend-mcp.patch`, `docs/p5-analytics-tool-kela-routes.patch`. See `docs/VI_SIDE_PATCHES.md` for apply instructions. Full P0 exploration notes at `docs/VI_INTEGRATION_NOTES.md`. ### Themes FundOS and VDR share **one light theme**: `#1a73e8` accent on `#f8f9fb` bg. No more dark vs. light split. The CSS is driven by `--fundos-*` vars in `templates/fundos/base.html`. ### Sidebar visibility (admin-controlled) - VDR sidebar defaults to a minimal set: Home, Q&A, Notifications, FundOS, Analytics, Intelligence. - Admins toggle optional tabs at `/admin/sidebar-tabs`: DD, Reports, Legal Drafts, Room Monitor, Compare, Marketplace, ODD, Portfolio. - Core tabs (Home, Q&A, Notifications, FundOS) are always on. - Stored in `BrandingSettings.visible_sidebar_tabs` (CSV, NULL = defaults, `"all"` = legacy). - Enum + helper: `SIDEBAR_TAB_CATALOG` + `sidebar_tabs_for()` in `app/models.py`. ## Code Architecture ``` app/ __init__.py # App factory, registers ~40 blueprints, DB migrations in _migrate_db() models.py # All SQLAlchemy models (3000+ lines — one file deliberately) services/ cfo.py # Pure-Python P&L + European waterfall math cim.py # CIM synthesis (Gemini) odd_byod.py # ODD generation over inline docs or MCP BYOD pricer.py # Pricing math routes/ api.py # REST API v1 (/api/v1/*) — @api_auth decorator auth.py # Login/logout, MFA, _post_login_landing() → FundOS fundos.py # FundOS home, MODULE_REDIRECTS, Deal Team /fundos/team fundos_api.py # /api/v1/fundos/* (all 10 modules, Bearer auth) fundos_docs.py # /api/docs reference page (7 groups incl. agents/vi/webhooks) lp_crm.py # LP CRM + per-question Accept/Edit/Drop + Autopilot trigger crm.py # Deal CRM + contact bulk import + /fundos/crm/screen (Agent B) cim.py # CIM + Quill artifact panel + /section//regenerate|suggest transactions.py # With VDR-room picker + //extract-cps (Agent D) investor_portal.py # + /calls//schedule-reminders (Agent E) vdr.py # + /room//diligence/run (Agent C) risk.py # + /agent/scan (Agent F) agent.py # AgentRun SSE stream + events.json polling fallback copilot.py # POST /fundos/copilot/ask (CSRF-exempt, JSON) tools_api.py # /api/v1/tools — tool registry + gemini-declarations vi_integration.py # /admin/integrations/ventureinsights/* vi_webhooks.py # /webhooks/vi/* (CSRF-exempt, HMAC auth) invite_links.py # /admin/invite-links + /invite/ mcp_connections.py # FundOS MCP connection CRUD admin.py # Admin hub + /admin/sidebar-tabs + /admin/usage-traces help_bp.py # /help page + /help/ask AI assistant (extended prompt) ... services/ agent_runtime.py # start_run / emit_event / propose_action / finish_run / events_after agent_actions.py # @action_handler registry (6 handlers) lp_autopilot.py # Agent A deal_screener.py # Agent B diligence_agent.py # Agent C closing_coordinator.py # Agent D cfo_agent.py # Agent E risk_agent.py # Agent F deal_team.py # Agent G orchestrator calendar_sync.py # Calendar event sync (Google OAuth), provider detection, deal matching meeting_summary.py # Phase 4 — Gemini post-meeting summarisation (auto + manual) folder_templates.py # Deal data room folder template packs (seed_folders) rag.py # Hybrid RAG — gemini-embedding-001 (3072d), ground_questions() tools/ # @tool decorator registry (14 tools) vi/ # VentureInsights integration (client + importers + enrichment + webhooks + reconciler + outbound) templates/ fundos/ # FundOS module templates (base.html is the FundOS chrome) base.html # Light theme, top module bar — extended by every FundOS template lp_crm/ # LP CRM templates crm/ # Deal CRM templates transactions/, cim/, cfo/, risk/, syndication/, investors/, pricer/, vdr/ admin/ sidebar_tabs.html # Admin sidebar toggle UI base.html # VDR base (used by non-FundOS pages) mcp_server.py # Kela-as-MCP-server (24 FundOS tools + 9 resources) ``` ## Key Routes (current) | Route | Purpose | |-------|---------| | `/` | Redirects to `/fundos/` when logged in | | `/fundos/` | **FundOS home** (10 module tiles) | | `/fundos/team` | **Agent G** Deal Team orchestrator (natural-language) | | `/fundos/agent/` | List recent AgentRuns | | `/fundos/agent/runs/` | Live event stream + approval cards | | `/fundos/agent/runs//stream` | SSE endpoint (?since= for resume) | | `/fundos/agent/runs//events.json` | Polling fallback when SSE is blocked | | `/fundos/agent/actions//approve` \| `/reject` | POST — execute/reject an AgentAction | | `/fundos/copilot/ask` | POST — context-aware chat (JSON, CSRF-exempt) | | `/fundos/lp-crm/` | LP CRM index + "Run Autopilot" (Agent A) | | `/fundos/lp-crm/meetings//followup/answer//accept\|edit\|drop\|restore` | Per-question Accept/Edit/Drop state | | `/fundos/crm/screen` | Paste pitch → run Agent B → AgentRun | | `/fundos/vdr/room//diligence/run` | POST — run Agent C | | `/fundos/transactions//extract-cps` | POST (term_sheet_text) → run Agent D | | `/fundos/investors/calls//schedule-reminders` | POST — run Agent E | | `/fundos/risk/agent/scan` | POST — run Agent F | | `/fundos/cim//section//regenerate\|suggest` | POST — per-section AI actions | | `/fundos/crm/stages` | Pipeline stage editor — rename/recolor/add/delete/reorder (org admin + per-user hide) | | `/fundos/crm//create-room` | POST — create data room from deal with folder template seeding | | `/fundos/crm/calendar/` | Google Calendar connect + upcoming meetings list | | `/fundos/crm/calendar/connect/google` | Start Google OAuth (calendar.readonly + userinfo.email) | | `/fundos/crm/calendar/callback/google` | OAuth callback — saves token, initial sync | | `/fundos/crm/calendar//sync` | POST — manual calendar sync | | `/fundos/crm/calendar/cron/sync` | Vercel cron every 5 min — sync all connected calendars | | `/fundos/crm/calendar/meetings//notes` | POST — structure raw notes with Gemini | | `/fundos/crm/calendar/cron/meeting-summaries` | Vercel cron every 15 min — auto-summarise ended meetings | | `/webhooks/inbound-email` | Inbound pitch email → triage agent → Deal (SES/SNS, CSRF-exempt) | | `/admin/integrations/ventureinsights` | VI connection setup + rotate secret + backfill buttons | | `/admin/integrations/ventureinsights/import/companies\|funds` | VI bulk backfill | | `/webhooks/vi/` | Inbound VI webhook (HMAC-signed, CSRF-exempt) | | `/webhooks/vi/cron/reconcile` | Daily reconciler (CRON_SECRET bearer) — 03:00 UTC | | `/webhooks/vi/_health` | GET — VI admin sanity-check | | `/admin/invite-links` | Shareable workspace URLs admin | | `/invite/` | Public preview + accept | | `/admin/usage-traces` | CreditLedger + per-agent trace drill-down | | `/admin/rag` | RAG index admin | | `/api/v1/tools` | Tool registry (14 tools) — GET lists, POST //call invokes | | `/api/v1/tools/gemini-declarations` | Gemini-ready functionDeclarations | | `/api/docs` | Public REST API reference (7 groups: 10 FundOS modules + agents + copilot + vi + webhooks_outbound) | | `/help` | Help + AI assistant (prompt extended with all new features) | | `/developer` | **Power-user platform landing** (logged-in, plan-gated). Anti-SAP hero ("Meet your fund where it is. AI bends Kela to your workflow"), 3 pillars (Connectors / Agents / Composable Modules), Marco/Mexico/PC vignette, SAP-failure-mode callout — above the existing card grid for Dashboards, Automations, Connected Apps, AI Assistant, Embed, Advanced. | | `/vision` | **Public manifesto** (no login required, `app/routes/public.py::vision`). Anti-SAP thesis with fund-admin/PC-fund/EM-VC examples, recruiting CTA at work@kela.com. Standalone HTML — does NOT extend `base.html`. | ## Authentication - **Web**: Flask-Login + Flask-WTF CSRF. Post-login → `_post_login_landing()` returns `/fundos/`. - **REST API**: `Authorization: Bearer vdr_` (or a JWT). API keys stored SHA-256-hashed. - **MCP**: Same bearer. The in-tree `mcp_server.py` forwards to `/api/v1/*`. ## Gotchas / Lessons Learned ### Gemini 2.5 Flash "thinking" budget **Always set** `thinkingConfig: {thinkingBudget: 0}` in `generationConfig`. 2.5 Flash's thinking mode consumes output tokens *before* writing the user-visible reply — with the default it ate ~862/900 tokens and truncated emails at 150 chars. Every call site in: - `app/routes/lp_crm.py` (follow-up drafter + enrichment) - `app/routes/crm.py` (bulk-import column mapping) - `app/routes/help_bp.py` (help AI) ...now sets `thinkingBudget: 0` and `maxOutputTokens: 1200–2500`. ### Nested `
` tags silently lose buttons Browsers strip inner `` tags per HTML spec. Any button "inside" gets attached to the outer form. Fix: flat form + per-button `formaction`. ### `password_hash` column is varchar(128) `werkzeug.security.generate_password_hash()` defaults to scrypt → outputs >128 chars → DB insert fails. Use `User.set_password()` which uses bcrypt (~60 chars), or pass `method='pbkdf2:sha256'` to generate_password_hash. ### Deleted SSO blueprint left dangling `url_for` calls `templates/admin/sso.html` referenced `sso.microsoft_callback` which no longer exists. Any `url_for('sso.*')` call will raise BuildError and 500 the page. Fix: build URLs from `request.host_url` for dead endpoints. ### Base template shadows view variables `templates/fundos/base.html` has `{% set modules = [...tuples...] %}` for the top-bar. A child template's `{% for m in modules %}` will iterate the tuples, not the string list the view passed. Use a different variable name in the view (e.g. `module_keys`) or shadow-protect with a default-list fallback. ### VDR `#sidebar` first form action="/help" `document.querySelector('form').submit()` in JS picks that first form and redirects to `/help?q=`. Always target forms by a specific attribute (`input[name="tabs"]`, `action`, etc.). ### VI `bearer_encrypted` is actually an X-API-KEY `VIConnection.bearer_encrypted` stores an API key that 8vdx-api's `HeaderApiKeyStrategy` expects as `X-API-KEY: ` — NOT `Authorization: Bearer`. `VIClient._headers()` sends it correctly; the column name is historical. Don't rename without a migration. ### MerchantAI / VantEdge data leakage risk 8vdx-api serves three products from one DB. Every VI call must either send `X-Product-Scope: ventureinsights` (defense in depth) or rely on the API key's `vc_firm_id` scope. The `VIClient._headers()` already sets the header; keep it. ### Deal → Room via Transaction, NOT directly `Deal` has no `linked_room_id`. To find a Deal's room, look up `Transaction` with `Transaction.deal_id == deal.id AND Transaction.linked_room_id IS NOT NULL`. Both `app/services/diligence_agent.py` and `app/routes/copilot.py` use this pattern. ### Webhook idempotency via DB UNIQUE, not app logic `VIWebhookEvent.event_id` has a UNIQUE constraint. Concurrent deliveries of the same event ID lose the race at the engine layer — catch the IntegrityError in the receiver and return `200 {duplicate: true}`. Don't add application-level locks. ### Resumable runs need BOTH seedEventId AND lastEventId `static/js/resumable-run.js` seeds `lastEventId` from the SSR-rendered events max id so a fresh page load doesn't replay. On subsequent reconnects it reads from localStorage (`agent_run__last_event`). Don't clear that key unless the run changes. ### `dealteam` rollback discipline `app/services/deal_team.py` explicitly calls `db.session.rollback()` at entry and after each sub-agent error. Sub-agents may leave an aborted transaction that breaks subsequent `emit_event` calls. Always `try/except` around child agent invocations and rollback on exception. ### Session-based routes 401 via Bearer auth `/api/v1/tools` uses `@api_auth` (Bearer only). Session cookies are rejected. Test with a real APIKey row (sha256 hash of plaintext stored in `key_hash`). For browser testing, add a test key to the DB, don't try to piggyback on session auth. ### Vercel serverless OAuth: never use `session[]` across redirects Flask session cookies don't reliably carry across two different Lambda invocations (different instances, SameSite=Lax edge cases). Symptom: "Invalid OAuth state" on callback. Fix: HMAC-signed stateless state tokens — base64url(json{nonce,exp}) + `--` + hmac.sha256(SECRET_KEY, data). See `_make_cal_state()` / `_verify_cal_state()` in `app/routes/calendar.py` (same pattern as `_make_state()` in `auth.py`). ### `_apply_view()` strips hidden stages — use `all_stages` for deal badges `_apply_view(_stages_for_org(org_id), user_id)` returns only non-hidden stages. The deal detail stage badge should always show even if the user hid that stage column. Solution: pass both `stages=_apply_view(...)` (for the kanban/edit form) AND `all_stages=_stages_for_org(org_id)` (for the badge). Template does `{% for s in (all_stages or stages) %}`. ### Worktree server doesn't auto-pull from main repo The Flask dev server runs from `.claude/worktrees/zen-hypatia` (branch `claude/zen-hypatia`). Commits pushed to `main` don't appear there automatically. To sync: move untracked files aside, `git pull vdx main`, restore them. Or make all edits directly in the worktree path. ### `stripe_configured()` is True locally (`.env` has the key) The guard `{% if stripe_configured %}` on billing top-up buttons is correct. Locally it evaluates to True (STRIPE_SECRET_KEY is in `.env`) so buttons show. On prod without the key they hide and show an info message instead. Don't remove the guard. ### APScheduler jobs must catch `OperationalError` on the first DB call On a local laptop where the dev DB hostname can't be resolved (off-network, deleted DO instance, DNS hiccup), APScheduler dumps a multi-screen SQLAlchemy stack trace every 5 min. `app/sync_engine.py::run_all_syncs` now wraps the first `SyncConfig.query.filter_by(...).all()` in `try/except OperationalError → log.warning + db.session.rollback() + return`. Fixed in commit `f67a20a`. Reusable pattern for any new APScheduler job that touches the DB. Note: APScheduler is disabled on Vercel (`scheduler = None` when `IS_VERCEL`), so this only matters for local dev — but ship the pattern anyway because it's the correct shape. ### Public marketing pages don't extend `base.html` `base.html` is the logged-in chrome (sidebar, branding, csrf-token meta). Public marketing pages like `/`, `/pricing`, `/vision` use standalone HTML with their own minimal `