Fundraising OS · Prompt Fix
One candidate per firm
The longlist builder created a separate candidate row for every partner it found at each firm, filling the round table with 51 duplicate rows. These prompt changes enforce one candidate per firm.
The bug
During activation, step 5 identifies multiple target partners per firm (decision-maker, subject expert, alternates). Step 6 then created a candidate row for each partner instead of one per firm.
Result: General Catalyst had 11 candidate rows, Valor Equity had 7, Sequoia had 6. The round table became unusable, filled with blank "Undecided / Fill / Longlist" entries that all pointed to the same firm.
The people directory was correct (all partners tracked there), but the round table should show one row per firm with the primary target contact.
The fix: 3 files
File 1: shared/references/activation.md
The activation pipeline reference. Changes to steps 5, 6, and the verification checklist.
5. **Target partners** — likely decision-maker, subject expert, alternate
partner per firm, with reasons.
5. **Target partners** — for each firm on the longlist, identify the likely
decision-maker (primary target), a subject expert, and an alternate partner,
with reasons. All partners go into the people directory (step 6.2), but only
the PRIMARY target becomes the candidate's linked `person_id`.
2. For each target partner, `POST /api/people` with `current_firm_id` set to
that firm's returned id, and KEEP the returned person `id`.
3. Build each candidate with `firm_id` and `person_id` set to those saved
ids. A candidate for a named investor MUST carry a real `firm_id`; set
`person_id` too whenever a target partner is known.
2. For each identified partner at each firm, `POST /api/people` with
`current_firm_id` set to that firm's returned id, and KEEP the returned
person `id`. ALL known partners go into the directory so the Investors
page shows the full picture.
3. Build **exactly one candidate per firm** with `firm_id` set to the firm's
id and `person_id` set to the PRIMARY target partner's id from step 5.
Additional partners are already visible in the Investors directory via
their people records; they do NOT get their own candidate rows.
New block added after step 6.3:
**One candidate per firm (non-negotiable).** The round table shows firms, not
people. A founder evaluates whether to pursue General Catalyst, not whether
to pursue seven individual GC partners. Multiple candidate rows for the same
firm_id clutter the round table with duplicates and break the founder's
ability to manage the pipeline. If the primary contact changes mid-round,
update the candidate's `person_id`; do not add a second row.
New verification check added:
- **No two candidates share the same `firm_id`.** If they do, keep the one with
the highest pipeline stage (or the one whose person has the strongest
relationship) and mark the rest `disposition: "duplicate"`.
Import section updated:
and person in the directory, then link the candidate by id. Never make a founder
re-enter work they already did.
and person in the directory, then link the candidate by id. **One candidate per
firm applies to imports too.** If the import contains multiple contacts at the
same firm, create people records for all of them but only one candidate row,
linked to the primary contact. Never make a founder re-enter work they already
did.
File 2: investor-longlist/SKILL.md
The longlist builder skill. The directory-linking steps now explicitly separate people (many per firm) from candidates (one per firm).
1. `POST /api/firms` for the firm (with `website`); keep the returned `id`.
2. `POST /api/people` for the target partner with `current_firm_id` = that id;
keep the returned person `id`.
3. Post the candidate in the activation batch with `firm_id` and `person_id` set
to those ids, `approved:false`.
1. `POST /api/firms` for the firm (with `website`); keep the returned `id`.
2. `POST /api/people` for EVERY known partner at the firm with
`current_firm_id` = that id; keep each returned person `id`. All partners
belong in the directory so the Investors page shows the full picture.
3. Choose the **one primary target partner** per firm: the decision-maker most
likely to champion the deal. Record the others as alternates in the people
directory only.
4. Post **exactly one candidate per firm** in the activation batch with
`firm_id` and `person_id` (of the primary target) set to those ids,
`approved:false`.
New guard paragraph added:
**One candidate per firm (non-negotiable).** The round table is a pipeline of
firms, not a roster of people. A second candidate row for the same `firm_id`
is a data bug. If you identify three partners at Sequoia, create three people
records but one candidate row pointing at the primary. The founder sees
"Sequoia / Alfred Lin" in the round table; the Investors directory shows all
three partners under Sequoia.
File 3: shared/references/api-contract.md
The activation-batch section already warns about firm_id:null. Add a parallel warning about duplicate firm_id values:
**One candidate per firm.** The batch must not contain two candidates with the
same `firm_id`. If you have identified multiple partners at a firm, create
people records for each, but submit only one candidate row per firm, with
`person_id` set to the primary target. The round table is a pipeline of firms;
duplicate firm entries are a data bug.
Full replacement files
The complete updated text for each file is below. Copy each one wholesale.
activation.md (full replacement)
# Zero-touch activation (PRD 4)
Trigger: a single founder sentence in chat or the app empty state, e.g. "I want
to raise a $10M seed for my company". Extract stage, target amount, security
type, geography, constraints. Infer the rest from the company profile and
confirm in review. Never send the founder to a settings page first.
Target: first reviewable draft under 20 minutes; full draft with intro paths
under 4 hours. Run as a resumable, checkpointed job.
## Pipeline
1. **Resolve host company** — extract structured attributes from the website,
deck (if provided), and approved sources. Write `host_company.profile`.
2. **Scaffold the round** — infer defaults (security, valuation posture, lead
requirement, mix, timeline). `POST /api/rounds` status `draft`.
3. **Competitor map** — generate category/model/buyer/competitor terms; classify
each direct/adjacent/substitute/potential-partner/uncertain with evidence
(activate `competitor-map`). `POST /api/competitors` for each, with `website`.
4. **Investor longlist** — deterministic hard filters FIRST (stage, geography,
investor type, check range, exclusions), then score survivors on visible fit
factors; assign tier (activate `investor-longlist`).
5. **Target partners** — for each firm on the longlist, identify the likely
decision-maker (primary target), a subject expert, and an alternate partner,
with reasons. All partners go into the people directory (step 6.2), but only
the PRIMARY target becomes the candidate's linked `person_id`.
6. **Write the directory FIRST, then link candidates to it.** This is the step
that keeps the round table readable. Do it in this exact order:
1. For each firm on the longlist, `POST /api/firms` (with `website`) and
KEEP the returned `id`.
2. For each identified partner at each firm, `POST /api/people` with
`current_firm_id` set to that firm's returned id, and KEEP the returned
person `id`. ALL known partners go into the directory so the Investors
page shows the full picture.
3. Build **exactly one candidate per firm** with `firm_id` set to the firm's
id and `person_id` set to the PRIMARY target partner's id from step 5.
Additional partners are already visible in the Investors directory via
their people records; they do NOT get their own candidate rows.
The investor identity lives ONLY in `firm_id` + `person_id`, never in
`next_action`. `next_action` is a short verb task for the founder ("Draft
intro request", "Confirm check size"), not the investor name. A candidate
whose firm/person is written into `next_action` renders as "Unlinked" with no
partner and breaks the Investors directory. Never do it.
**One candidate per firm (non-negotiable).** The round table shows firms, not
people. A founder evaluates whether to pursue General Catalyst, not whether
to pursue seven individual GC partners. Multiple candidate rows for the same
firm_id clutter the round table with duplicates and break the founder's
ability to manage the pipeline. If the primary contact changes mid-round,
update the candidate's `person_id`; do not add a second row.
7. **Conflict screening (the default deliverable).** Run the multi-population
investor conflict screen. The 2026 Forbes Midas List is ALWAYS included by
default (every founder can dream), and you add every other relevant
population for this company: strategics, family offices, international VCs,
growth/PE, angels, crossover, sovereign. For each investor, run the
five-layer assessment against the competitor map and tag it Exclude / High
caution / Low fit / Keep eligible, with conflict type, known exposure,
firm-level + person-level issue, $10M-lead fit, bottom line, permitted
access, confidence, and sources. Post it with `POST /api/conflict-screen`
(segment + source_list per investor). This is what a client founder sees
first when they say "raise $Xm". Reuse `conflict-assess` for the per-investor
five-layer reasoning.
8. **Intro paths** — search the relationship graph for direct + two-hop paths
(activate `intro-path`).
9. **Strategy brief** — sequencing: which tier-1 anchors to run in parallel and
why, which conversations set price, which are fill, timing windows. Flag when
anchors are spread too far apart to create price competition.
10. **Thesis memo** — write the founder's fundraising thesis (activate
`content-writing`) and `POST /api/thesis` with the `distilled` fields (ask,
why now, use of funds, why us, anchors). The Thesis tab must not be empty
after activation.
11. **Review batch** — `POST /api/activation-batch` with `approved:false` and
every candidate carrying its `firm_id`/`person_id` from step 6. The founder
bulk-approves tiers, edits inline, or rejects with a structured reason
(feeds the exclusion taxonomy).
Publication rule: only approved records enter the active round.
## Activation is not done until every deliverable exists (verify before you stop)
A partial run that posts candidates and stops is a failed run: it leaves the
Investors, Conflict screen, and Thesis tabs empty and the round table
"Unlinked". Before you declare activation complete, `GET /api/state/<slug>` and
confirm ALL of the following. If any check fails, finish that step and re-verify;
do not report success.
- The round is present and the candidate count matches the longlist.
- EVERY candidate has a non-null `firm_id`. Zero candidates render "Unlinked".
- Named-partner candidates have a non-null `person_id`.
- No candidate has an investor name sitting in `next_action`.
- **No two candidates share the same `firm_id`.** If they do, keep the one with
the highest pipeline stage (or the one whose person has the strongest
relationship) and mark the rest `disposition: "duplicate"`.
- The conflict screen exists (`GET /api/state` shows it, or the Conflict screen
tab is non-empty) with rows for the screened populations.
- The competitor map has rows.
- The thesis exists (Thesis tab non-empty).
- The Day-1 checklist exists (Today is non-empty) — see below.
## Alternate entry: import (PRD 4.4)
A founder mid-raise activates by importing a spreadsheet instead (activate
`import-mapper`): preserve every original column as mapped or archived data,
resolve entities, flag duplicates, then run steps 3-11 against the imported
pipeline. Imported rows follow the same rule as step 6: create/resolve the firm
and person in the directory, then link the candidate by id. **One candidate per
firm applies to imports too.** If the import contains multiple contacts at the
same firm, create people records for all of them but only one candidate row,
linked to the primary contact. Never make a founder re-enter work they already
did.
## Document seeding (PRD 4.5)
Founders arrive with prior work product (strategy memos, research packets,
conflict maps, target lists). Activate `workspace-seed` to parse them into
proposed records into the SAME review batch. Every seeded fact carries the
source document, an excerpt, and the `unverified-import`/`workspace-authored`
class; nothing seeded is presented as verified public fact. Re-seeding an updated
version diffs against the prior seed instead of duplicating.
## Generate the Day-1 checklist (mandatory, never leave Today empty)
On activation, ALWAYS post a starter to-do checklist for the founder via
`POST /api/checklist` (founder_slug + items). Generate it from the round + the
conflict screen + the anchor plan, the concrete next actions the founder should
take this week (send the specific intro notes, run the primacy question with the
conflicted-but-capable names before the data room, prep the data room, line up
the co-lead, etc.). A founder should never land on an empty Today. Keep it short
(6-9 items), specific, and derived from THIS round's actual state, not generic.
investor-longlist/SKILL.md (full replacement)
---
name: investor-longlist
description: >-
Generate and score the candidate investor universe for a round, then assign
each firm a tier (tier-1 anchor, price-setter, fill, angel, strategic).
Deterministic hard filters run before semantic scoring; every fit score breaks
into visible factors and every exclusion carries a stated reason. Use during
activation and whenever the round is edited.
secrets:
BOO_APP_DOMAIN: vault://default/kv/bundle/dns/domain/value
FOS_API_TOKEN: vault://default/kv/bundle/fundraising-os/api-token/value
---
# Build the longlist
> **Calling the app.** Writes go through the `fos()` helper and `FOS_URL` from
the parent skill (`../boo-fundraising-os` SKILL.md, "The API you drive"); the
request/response shape for every endpoint (here, `POST /api/activation-batch`
for proposed candidates and `POST /api/firms` / `POST /api/people` for the
directory) is in `../boo-fundraising-os/shared/references/api-contract.md`.
Read `../boo-fundraising-os/shared/references/information-model.md` (tiering)
and `research-rules.md`. Apply deterministic hard filters FIRST (stage,
geography, investor type, check range, exclusions), then score survivors on
visible factors: stage fit, check fit, sector, geography, business model,
portfolio relevance, lead behavior, and recognition standing (a visible,
editable weight). Assign a tier and state, in plain founder-editable language,
which tier-1 anchors to run in parallel and why. Scoring never uses protected
characteristics or demographic proxies.
## Write the directory, then link every candidate to it
Each firm on the longlist becomes a real directory row that the candidate points
at. Do it in order and NEVER skip it:
1. `POST /api/firms` for the firm (with `website`); keep the returned `id`.
2. `POST /api/people` for EVERY known partner at the firm with
`current_firm_id` = that id; keep each returned person `id`. All partners
belong in the directory so the Investors page shows the full picture.
3. Choose the **one primary target partner** per firm: the decision-maker most
likely to champion the deal. Record the others as alternates in the people
directory only.
4. Post **exactly one candidate per firm** in the activation batch with
`firm_id` and `person_id` (of the primary target) set to those ids,
`approved:false`.
**One candidate per firm (non-negotiable).** The round table is a pipeline of
firms, not a roster of people. A second candidate row for the same `firm_id`
is a data bug. If you identify three partners at Sequoia, create three people
records but one candidate row pointing at the primary. The founder sees
"Sequoia / Alfred Lin" in the round table; the Investors directory shows all
three partners under Sequoia.
A named-investor candidate MUST carry a real `firm_id` (and `person_id` when
the partner is known). The investor name lives in the linked firm/person, never
in `next_action`. It is a short founder task like "Draft intro request". A
candidate posted with `firm_id:null` renders "Unlinked" and is missing from the
Investors directory. See
`../boo-fundraising-os/shared/references/activation.md` step 6.
api-contract.md (add to activation-batch section)
Insert this paragraph after the existing next_action warning, before the JSON example:
**One candidate per firm.** The batch must not contain two candidates with the
same `firm_id`. If you have identified multiple partners at a firm, create
people records for each, but submit only one candidate row per firm, with
`person_id` set to the primary target. The round table is a pipeline of firms;
duplicate firm entries are a data bug.
Bug found in the Boo Lab Inception Round (Aug 2026). 22 firms had multiple candidate rows, 51 total duplicates. Worst offenders: General Catalyst (11), Valor Equity (7), Sequoia (6), a16z (4). Root cause: activation step 5/6 created one candidate per partner instead of one per firm. The API endpoints (POST /api/firms, POST /api/people, POST /api/activation-batch) also lack server-side dedup, which is a separate code fix.