DocsEdge Cases
find_investors · Edge Cases

Edge Cases

24 documented edge cases across input, filter, synthesis, pipeline, and UI.

Edge Case Map

Twenty-Four Decision Branches Across the Pipeline

Things that go wrong in the real world. We need a documented behavior for each before we ship.

Input edge cases

#CaseBehavior
1User says "investors" but means "advisors"Classifier returns confidence < 0.7 → ask back: "investors who write checks, or advisors?"
2No deck uploaded, only a one-linerSkip thesis-extract, run on the one-liner directly. Tag results confidence: low_thesis.
3Deck uploaded but not a fundraising deck (e.g. all-hands)Thesis-extract returns null → fall back to one-liner mode, surface a warning card.
4User specifies stage but stage is impossible (Series A medtech with $50M ARR)Classifier flags stage_mismatch → ask back.
5Multilingual queryTranslate to English first, run pipeline, do NOT translate WHY back (English investors, English memo).
6Empty / single-word query ("investors")Classifier returns count=0, confidence=0.3 → render conversation starter cards instead.

Filter edge cases

#CaseBehavior
7Filters too narrow → 0 hitsRender "no matches under these filters" + 3 suggested loosenings (drop geo, expand stage, raise check ceiling)
8Filters too broad → 5000+ hitsScaNN LIMIT 25 already truncates. Surface a banner: "showing top 25 of N — narrow filters?"
9Recency-of-contact filter excludes a strong matchKeep the match but tag recent_contact: true, demote in ranking
10User in a region with no investor coverage in graphSurface honestly: "graph has no investors in your region. Closest coverage: …"

Synthesis edge cases

#CaseBehavior
11Investor matches on vector but has no recent activityDemote, tag dormant_investor: true. Don't drop.
12Investor matches but the WHY is generic ("strong fit")Reject the WHY pass output, retry with stricter prompt OR drop the card.
13Two investors at the same firm both scoreGroup as one card, list both partners.
14Investor is on banned-phrases list (former relationship gone bad)Filter pre-rank. User's banlist lives in Zep facts.
15Opus drops a banned phrase ("worth a 20-min call")Lint pass before stream — reject + retry.

Pipeline edge cases

#CaseBehavior
16OpenRouter returns 429 (rate limit)Fall through provider order automatically (Fireworks → Together)
17OpenRouter returns markdown-fenced JSONStrip fences before parsing (XanoScript gotcha: ```json → "[object Object]" if not stripped)
18AlloyDB connection drops mid-queryRetry once, then surface error card
19Embedding API returns wrong-dim vectorReject, retry (1536 dims required)
20Zep thread.get_user_context times outContinue without context, log it, don't block dispatch

UI edge cases

#CaseBehavior
21User refreshes mid-streamServer should not lose state — Zep already has the user message
22User cancels (abort)Close stream controller, do NOT record assistant message in Zep
23User clicks card during streamIgnore until stream completes — no race
24Backend stream never closesisRunning stuck true forever — see crayon-sdk gotchas.md

Open questions for Apr 29

  • For #1 (ambiguous intent), does the classifier ask back, or does the UI surface 2-3 picker options?
  • For #11 (dormant investors), what's the cutoff? Mark mentioned 18-month directive on Apr 20 — confirm.
  • For #14 (banned-relationship list), is that a Zep fact, a separate table, or a user pref?
  • For #21 (refresh mid-stream), do we resume the stream or restart from the user message?