Install
openclaw skills install @chainleo/investor-searchName a country, get its verifiable investors: family offices, VCs, angels. Rejects advisers and lawyers. Every field is sourced, gaps stay empty. Nothing guessed. Stops on evidence.
openclaw skills install @chainleo/investor-searchAsk any agent for family offices in a country and it returns the number you asked for, not the number in the country. This skill replaces it with a rule, and records where every field came from and how well it was checked.
This file is the rules. Two of the reference files are read while working, not
afterwards: open reference/lists.md before round 1 to add this market's word forms, and
reference/environment.md when you write the output or resume from a paste. why.md is for
when a rule looks arbitrary.
| file | when |
|---|---|
SKILL.md | working — every operative rule, nothing else |
reference/lists.md | legal forms, generic tails, place words, type words — extend per market |
reference/why.md | the measurements and the failures that produced each rule |
reference/environment.md | where files go, the ledger format, delivering a file to the user |
0 Is it an investor? reject the advisers who sell to them
1 Is the search finished? stop on six dry rounds, not on a number
2 Does it clear the bar? name · not a dupe · source · headquarters
3 Is it one firm or two? fold to compare — merge only with proof
4 What is it? classify from evidence, never from the name alone
5 What is still missing? fill the same row across rounds; empty stays empty
The memory of this skill is its files. Deduplication, the pending queue, the round history and the exhaustion gates all read from disk. An agent that cannot write still runs every judgement here — Job 0, the fold, the provenance rules, the language test, empty stays empty — it simply cannot remember making them.
Do not try to classify the environment. Test it, and report what actually happened. Persistence is not observable today. What is: whether a read finds something, and whether a write succeeds.
Everything lives under one predictable path, because nothing tells an agent which files it already has — so the names are fixed here rather than chosen per run.
investor-search/index.md one line per market held
investor-search/<market>/investors.csv
/sources.csv
/investors-pending.csv
/investors-rejected.csv
/rounds.csv
/ledger.txt
Write relative paths and let the host place them. On hosts with a persistent working directory they land in it and are still there on a later run.
<market> is the English short name, lowercase, hyphens for spaces — poland,
united-arab-emirates. The same market must produce the same folder every time, or the
next run reads an empty path and starts from zero while the data sits one spelling away.
index.md — the one path that never variesEverything else is found through it, so it is the only file guaranteed to be findable. Plain markdown, one line per market:
# Investor search — markets held
| market | scope | firms | pending | last run |
|---|---|---|---|---|
| poland | investment firms headquartered in Poland | 46 | 12 | 2026-03-14 |
scope is not decoration. Family offices headquartered in Poland and investors active
in Poland are different populations, so a round history built for one does not describe the
other. If the request's scope differs from the stored one, do not silently continue — say
what is on file, say what was asked, and let the user choose: continue the stored scope, or
start a second folder. A filter on the same population (…that invest in software) is not a
scope change; it stays in the same folder. reference/environment.md has the naming rule for
second folders, and for regions, multi-country and sub-national requests.
Write index.md before round 1 with the row you are about to work on, firms and
pending left at the previous values or blank, and update it again at the end.
Read index.md first, then every file in the market folder — investors.csv,
sources.csv, investors-pending.csv, investors-rejected.csv, rounds.csv. Read them
before the first search, not after it.
| what you find | what to say and do |
|---|---|
| files for this market | "Continuing a run of 14 March — 46 firms held." Load them, resume |
index.md but not this market | a first run for this market; other markets stay untouched |
| nothing | a first run. Say that too — silence reads as continuity |
index.md lists the market but the folder is gone | treat as a first run, and say the index disagreed with the disk |
| a file is short, truncated or unreadable | load what parses, name the file that did not, and shut Gate 0 — a partial round log that reads cleanly is the one input nothing else can check |
Before round 1, open reference/lists.md and add this market's legal forms, generic tails
and place words. Most field tests skipped this, and the fold failed on local names.
Create or update index.md before round 1. If writing is going to fail, learn it after
one minute rather than after forty rounds of work you are about to lose.
"I cannot write files here, so nothing will carry to a later run. I will print the CSV and a ledger into the chat instead."
A silent write failure is the worst outcome available (why.md). If a previous run's
file does not come back, the run is a first run, whatever the write said last time.
checked is on every row
precisely so this can be said.id to different firms — so a
blind merge reattributes citations rather than combining work. If the file on disk is no
longer the one you loaded, write yours beside it as investors-<ISO date>.csv, say
plainly that two runs overlapped, and leave the reconciliation to a person who can see both.
Never overwrite a file you read forty rounds ago.id is permanent. Never reissue one, never renumber on rewrite — sources.csv
joins to it, and a renumbered file silently reattributes every citation.investors-rejected.csv, which is a list of named firms carrying a negative
claim and was never meant to travel.History truncates silently. The files, or a pasted ledger, or nothing — never the transcript.
Where nothing persists, end the run with a ledger and say plainly what it does:
deduplication next time, and nothing else. Format, delivery and how to read one back are in
reference/environment.md.
Run this first, on every candidate. A search for "family office" in any language returns mostly firms selling family-office services. They have the words in their name, a real site and a real address, and they pass every other check.
Measured: 5 of 6 in one market, 52% of everything resolved in another. One targeted query returned 8 results, 8 advisers, 0 investors.
Does this organisation deploy capital, operate businesses, or sell services to people who do?
investor_evidence | what it is | keep? |
|---|---|---|
invests | deploys capital into things it does not operate | yes |
operating_group | family-owned, runs its own businesses, may also invest | yes — flagged |
syndicate | angel network / membership body that routes others' capital | yes — flagged |
unclear | you found the firm, and it is genuinely undecidable | yes — flagged |
| — | you never found enough of the firm to judge at all | no → pending, unresolvedJob0 |
| — | sells services to families | no → investors-rejected.csv, adviserNotInvestor |
| — | not an investment entity at all | no → investors-rejected.csv, notAnInvestor |
notAnInvestor is for an operating company that a directory simply listed wrongly — an
energy developer, a manufacturer, a consultancy with no family-office pretension. Different
from adviserNotInvestor, which describes a real adviser correctly excluded.
Investor — any one is enough: names portfolio companies or holdings · describes managing the wealth of a named family or founder · states a cheque size, stage or sectors it invests in · a registry or news source has it acquiring stakes.
Adviser — any one disqualifies: it is the private-client or family-office arm of a law firm, accountancy, bank or consultancy (check the parent brand) · it sells "services", "advisory", "consulting", "administration", "reporting", "succession planning", "incorporation", "licensing" to families · it invites you to become a client · its people are lawyers, accountants or corporate-services staff.
type: unknown, investor_evidence: unclear. Never
let the name decide it.| case | action |
|---|---|
| sovereign / state / government fund | pending, reason outOfScopeSovereign |
| a commercial subsidiary of a sovereign fund | keep it — classify by its own evidence (Job 4), and set sovereign_parent: <name> |
| a real capital pool named only as "the X family" | pending, reason namedFamilyNotFirm |
| a firm in the wrong country | pending, reason outOfCountry |
Record the parent so the reader can filter either way.
Find all of them and report the true number. A target is a ceiling on work, never a quota to fill. Take at most 10 new names from any one round. If 46 can be sourced and 30 were asked for, the answer is 46. If 12 can be sourced, the answer is 12 — and you say so:
"You asked for up to 30. 12 could be verified, so 12 is the answer — the rest were not found or could not be sourced, and I will not make up the difference."
Search widely and enumerate every firm you can actually source, up to the ceiling. "Fewer is correct" applies when you cannot source more. It is not a preference to aim for, and stopping at two or three when you could cite ten is a wrong answer.
One query, and the verification of what it returned. Define it or the streak counter measures nothing.
rounds.csv — round,query,surface,offered,survived,dry_streak.
Without it, the exhaustion sentence cannot be reconstructed by anyone but the agent that
was there, and a second run cannot see which surfaces were already tried.Six consecutive dry rounds is necessary and NOT sufficient. Measured: it fired at round 23 and was falsified at round 26 of the same run — 14% of the final firms arrived after.
"Six queries I chose stopped working" is not "this market is exhausted." That is the strongest sentence in this skill resting on its weakest evidence, and it has to be earned.
Gates 1 to 3 were written after that run and have not themselves been field-tested. Treat them as a design with an argument behind it, and keep saying so in the report. Gate 0 is different in kind — a precondition on the evidence, not a heuristic about searching.
All the gates below must be open before you may declare exhaustion:
You may not declare exhaustion from memory. Gate 1 reads investors-pending.csv and
Gate 2 reads rounds.csv. Without them the claim rests on the agent's recollection of its own
rounds — the unverifiable assertion this whole skill exists to replace. That run stops at
PARTIAL.
Written, read back, and located — all three. A write that returned success proves nothing on a host that quietly redirected it, so read the file again before you rely on it. And a file whose location is never stated cannot be checked by the person relying on it, so the report gives the path.
A pasted ledger does not reopen this gate. It carries names, which is enough to stop a duplicate; it does not carry a round history anyone can check.
You may not declare a market exhausted while investors-pending.csv holds names under a
resolvable reason — budget, sourceUnchecked, noHeadquarters, unresolvedJob0.
Terminal parks (out of country, sovereign, a family with no vehicle, an individual angel)
do not hold the gate: they are answered, just not as rows.
Measured: the rule fired while holding 75 regulator-confirmed names it had told itself not to count — a queue cannot reset the streak.
Drain the queue first. If the budget will not allow it, you did not exhaust the market — you ran out of budget, and that is the report you write.
Six dry web searches are one surface failing six times, not six independent failures. The dry streak must include at least three of these five:
1 general web search
2 a public company or regulator registry
3 an association / membership list
4 news, deal announcements, "led by"
5 a commercial aggregator or directory
The registry surface yielded 85 names on one fetch and the association surface 28 — after web search had gone dry. A surface that has never been tried cannot be dry.
Measured: one garbled fetch that was never recovered protected the streak counter but not the evidence — a mirror at round 26 held two of the three firms that broke the rule.
A garbled or unreadable page must be recovered — retry, a mirror, a cache, a search for the same headline, or any other route to the content — before the round it belongs to may count toward the streak.
"Garbled" means the bytes are wrong, not that the script is unfamiliar. Arabic, Chinese and Cyrillic pages are not garbled. If you cannot recover the content by any route, the round is excluded from the streak rather than counted either way, and the report says how many rounds were excluded.
Stop after six consecutive dry rounds. Say so, and say what you actually verified:
"Rounds 41–46 produced no new verifiable name, across web search, the commercial register and two association lists, with no names left pending. On those surfaces, this market is exhausted at 46."
(Illustrative. No shipped run has ever legitimately reached this sentence — see the gates above and
reference/why.md.)
Name the surfaces. The claim is only ever as wide as the surfaces you tried, and a reader who knows a sixth surface can then tell you so.
A round is dry when it produced zero firms that passed verification — not zero names offered.
| situation | dry? |
|---|---|
| names offered, all already held or already returned | yes |
| round produced ≥1 new verified firm | no — streak resets |
| round named only firms the directory seed already had | no — it did real work |
| page would not load / answer garbled | no, and the round does not count until the content is recovered by another route |
| round genuinely timed out | yes — but count it separately and report it |
One judgement can decide the whole streak — in one run a single Job 0 rejection was what made a round dry. When a streak hinges on one call, say which one in the report.
Ask the user for a budget. If they do not set one, use 25 rounds and say that you did. Sixty rounds is the hard backstop whatever the budget says — stop there and report a budget stop, whether or not any gate ever opened. When it runs out:
STOPPED ON BUDGET, NOT ON EXHAUSTION.
This result is PARTIAL. N names were offered and never verified.
Do not read the total as a count of what exists.
A partial run must never be reported in the same shape as a finished one.
Spend the last of the budget verifying, not searching. A shorter solid file beats a longer unverified one.
A request naming one type is a filter over a broad search, never a narrow search.
type.matches_request: yes on the ones that match the named type and no on the rest,
so the asker can filter to exactly what they asked for in one click.And the contradiction rule (Job 4) does NOT fire on this. A VC found under a
"family offices" request is not a contradiction — it is the broad search working. The rule
only ever applies to a type you inferred that disagrees with the request, never to one a
page states. Leave type_blanked_reason empty.
"Family offices in Austria that invest in AI" — do not search for that. A narrow query goes dry early for the wrong reason: most firms never put "AI" on a page that ranks.
phase 1 exhaust the BROAD set → 61 firms, six dry rounds
phase 2 filter what you already hold → 20 match · 9 unknown
Only phase 1 runs the exhaustion rule. Answer in both numbers, never the filtered one alone: "Austria has 61 family offices. 20 invest in AI. 9 do not say." Keep the whole broad set — the next filtered question is then free.
Same bar for a name you found and a name read off a directory.
| # | check | reject as | fatal |
|---|---|---|---|
| 0a | sells services rather than investing | adviserNotInvestor | yes → rejected file |
| 0b | not an investment entity at all | notAnInvestor | yes → rejected file |
| 1a | no firm name, but a real capital pool | namedFamilyNotFirm | no → pending |
| 1b | a named individual angel, no vehicle | individualNotFirm | no → pending |
| 2 | already returned this run | alreadyReturned | yes — drop silently, it is already on file |
| 3 | already in investors.csv | alreadyHeld | yes — drop silently, it is already on file |
| 4 | the cited page does not exist | sourceDead | yes |
| 4b | the page exists but could not be fetched | sourceUnchecked | no — keep the row |
| 5 | headquarters cannot be established | noHeadquarters | no → pending |
| 6 | wrong country / sovereign parent | outOfCountry / outOfScopeSovereign | no → pending |
Before anything else in this list: is this an organisation, or a vehicle one runs? A fund is not a row; its manager is. See Manager or fund.
| what happened | verdict |
|---|---|
| 404 · 410 · domain does not resolve | sourceDead — fatal |
| robots.txt disallows | sourceUnchecked — keep |
| paywall · login wall · cache-only domain | sourceUnchecked — keep |
| a redirect your fetcher would not follow | sourceUnchecked — keep |
| your own tooling refused the URL | sourceUnchecked — keep, and say so |
A sourceUnchecked row is honest and useful; a missing row is neither. But a firm whose
existence rests on a single unreachable page is not enough — find a second source, or send
it to investors-rejected.csv as unsourceable, and re-offer it on a later run, because
that code describes a page, not the firm.
If your tools cannot tell these apart, default to sourceUnchecked, and say so in the
report. Many agent fetch tools return one undifferentiated error. A rule you cannot
execute must fail towards keeping the row, not towards deleting it — this check decides
half your rejections.
Recovering a refused URL is worth it. Searching the URL into scope first costs roughly 2.4 tool calls per name, measured on one market — and recovers most of them.
1 the firm's own site — imprint, Impressum, contact, legal page
2 a public company registry
3 a reputable news or association page
4 a commercial aggregator or lead-gen directory ← never alone for existence
Two headquarters is a real answer, and the column holds one. Where a firm presents two equally — "based in X and Y" — record the one it lists first, name the second in the report, and never park the row: a firm with two offices still has a verifiable one. If the second is in another country, say so, because the market question is then on the row's face.
City is the target; country alone sets headquarters_partial. Never reject for a
missing city when the country is established.
source fetched · source NOT fetched · NOT in the source · no source · you told me
In: firms that passed · firms rejected as alreadyHeld · firms rejected as
adviserNotInvestor or notAnInvestor — both describe the firm, not the page, and will
not change on a later round.
Out — deliberately: sourceUnchecked · noHeadquarters · sourceDead · unsourceable.
All four describe one page on one day. Record their names anyway.
Ask and enforce — state exclusions in the next query and check returned names yourself.
Never merge the counts. "20 already known or already returned" welds two failures with opposite fixes. Report separate lines.
Do not self-censor. Never skip a firm for being well known. The check decides, not you.
Read it before round 1, but: every name goes into the exclude list · the seed must not answer the dry test · one seed name rescues one round, once, and the whole seed rescues at most three · a directory failing never stops the search.
A list of N named firms is N decisions already waiting. Spend the budget on those before spending it looking for more names, because Job 0 cannot run on a name — it needs the firm's own page, and an unresolved name helps nobody.
Measured: an association list gave 31 members in one round. The run then went looking for more names and reached the budget with 16 of those 31 still undecided — including several whose own pages would have settled Job 0 in one fetch each. One rejection was recorded where a dozen were sitting there unread.
This is not the same as trusting the list. It still does not answer the dry test, and its members still face every gate. It only says: decide what you already hold first.
If the budget will not cover the list, say how many were left undecided, by name, in
investors-pending.csv under unresolvedJob0. A number that large is itself the finding.
An investor often appears as several legal entities: a management company and the funds it runs, each separately registered and separately named.
One row is the entity that decides where the money goes — the manager. The funds it
operates are not separate rows, however many there are and however distinctly they are named.
Record the manager's name, and put a fund name in formerly only if the manager was renamed,
never to represent a fund.
One firm in one market ran three separately named funds. Three rows would have been three false positives in a list whose whole purpose is that one row means one organisation.
This is the fold table's fund row, stated in full. A manager's own site naming its funds
satisfies "a page says one is part of the other" word for word, so read literally the table
would have you keep all four rows with part_of set. It does not: a fund is a vehicle, and
part_of is for organisations.
The exception is where the fund and the manager are in different countries. A locally
registered fund run by a manager headquartered abroad is a real case and the market question
decides it: the row is the manager, and if the manager is out of country it is
outOfCountry — whatever the fund's registration says. State which you applied, because
this one is genuinely arguable and a reader may want the other answer.
Fold to compare. Keep the best-looking form to display. Lists live in
reference/lists.md.
é→e, ö→o, ü→u. Transliterate, never
drop: dropping turns André into andr, which matches nothing.ß→ss, ø→o, æ→ae.al · el · the · la · le · los · il · de · van · von.
Leading token only, never inside the name.s.r.o.,
s r o, sro and S.R.O. are one entry. Extend reference/lists.md with this
market's forms before round 1 — most field tests skipped that, and the fold
failed on local names.
⚠ Leading forms are not rare. Baltic and Nordic registries write AS Vesta while
the firm writes Vesta; the same list holds Vesta Capital AS. An end-anchored strip
folds one and not the other.
⚠ This step runs BEFORE punctuation collapse. The other order turns a.s. into
a s and it never matches as.
⚠ Jurisdictions are not legal forms — DIFC, ADGM, IFSC distinguish real firms.asia, europe, mena, emea, international,
global (reference/lists.md §3). Type abbreviations (vc, pe, fo, mfo) are
generic tails and are handled in step 8, under its distinctiveness guard.What remains is distinctive only if it contains a token that is 4+ characters, not on the legal-form or generic-tail lists, and not a place.
A place name is never distinctive. <City> Capital Group (a private investment group)
and <City> Investment Office (a government FDI agency) both reduce to the city name
alone. <City> + <generic financial noun> is the modal naming convention across the
Gulf and common in Asia and Latin America.
If you cannot decide whether a token is a place, treat it as a place and do not fold. Measured: five unrelated organisations folded to a single such token in one run. Refusing to fold costs a flag; folding costs the truth.
| same fold key, and… | action |
|---|---|
| same registrable domain, and no page says one is part of the other | merge |
| same domain, but a page says one is part of the other | keep both — set part_of. A conglomerate and its family office share a site |
| same domain, but one is a fund the other manages | one row, the manager. Not part_of — a fund is a vehicle, not an organisation. See Manager or fund, at the end of Job 2 |
| different domains | keep both, set fold_conflict |
| no domain on either | keep both, set fold_conflict |
Caught a five-way collision across four domains on one real run — the failure that, one version earlier, silently wrote five organisations as one row.
Duplicate rows are visible and a reader fixes them. One row for two companies is invisible.
A high flag rate is the rule working, not failing (why.md).
Same firm if: folded names identical · one adjacent transposition in a token · one inserted/dropped character in a token of 6+ characters.
A substituted letter is not a typo — Bauer/Baier are different families.
Except in transliteration: for names carried from another script, vowel substitution
is a typo (Nasiri/Nassiri/Nassery = one firm — invented, like every organisation name in this file); consonants must still match.
Never apply the typo rule across countries — only where the domain or headquarters agrees.
A firm that changed its name is not a typo, not a spelling variant and not a fold — the two strings have nothing in common. Three turned up in one small market. No normalisation will ever catch them.
The only things that do:
Set formerly on the row, keep the old name searchable, and put the old name in the
exclude list — otherwise it comes back every round as a name you have never seen.
City · sector · investor type · a shared person. Two firms in Hamburg investing in software are two firms. A shared surname is a family, not a merger.
Longest sourced value per field · all source rows kept · newest checked per field.
X (CFA) → X.reference/lists.md).Erika Mustermann = Erika M. Mustermann; ≠ Erika Maria Mustermann.Jan/Jon, Eric/Erik stay separate.family_office · investment_group · private_equity · venture_capital · angel · unknown
Plus sovereign, which is a marker, not a type — those go to pending.
Two different questions, two different answers:
reference/lists.md). An allowlist, not a denylist."venture capital fund" all owned → write
"investiční skupina" investiční, skupina owned → investment_group
"crypto fund" crypto not owned → refuse
"venture studio" studio not owned → refuse
The allowlist governs spelling, never identity. It cannot tell you a firm is a family
office — Job 0 does that, and Job 0 runs first. Otherwise a consultancy called
… FAMILY OFFICE types cleanly, because family and office are owned words.
unknown is a real answer and appears in the outputTreat any expected proportion of unknown as unknown — the rate varies enormously by
market. Never report it as a quality signal. Forcing a label onto an unknown is the same
mistake as inventing an email.
Only when no page states a type may you read the firm's own name, and only narrowly:
family office · familienbüro · familienunternehmen · rodinná kancelář → family_office
ventures · venture capital · standalone "VC" → venture_capital
Capital, Invest, Holding, Group, Gruppe, AG, GmbH resolve to nothing from
a name alone.
⚠ Outside the market this rule was measured in, prefer unknown. It typed one
individual's holding vehicle as venture_capital off the word Ventures. Whenever the type
came from the name, say so in provenance.
| the type is… | and it contradicts the request | do |
|---|---|---|
| stated by a page you fetched | keep it, keep the source, set matches_request: no | |
| inferred by you | blank it, record type_blanked_reason | |
unknown | nothing — it is already the empty answer |
Compare against the request, and only the request — never against prose, which fails on negation ("ist kein klassischer VC-Investor") and on parts (a venture arm inside a family office).
A request naming no type contradicts nothing. One naming the asserted type among others contradicts nothing either — ambiguity is not a contradiction.
Kept only for a user who asks narrowly anyway — Job 1 tells you never to issue such a request. Do not go looking for work for it.
round 1 name, country ← a directory listing
round 8 + website, headquarters, sectors ← the firm's own site
round 22 + person, role, LinkedIn ← its team page
Fill the same row. Never create a second row for the same firm.
https://. A national phone number stays national.Where writing works: the six files below, in investor-search/<market>/, plus the
investor-search/index.md that lets a later run find them.
Where it does not: print investors.csv and sources.csv into the chat as fenced CSV
blocks, and print the ledger. investors-rejected.csv becomes a count and nothing else —
printing a list of named firms carrying a negative claim is what that file's own rule
forbids. investors-pending.csv and rounds.csv are lost, and Gate 0 is therefore shut.
Handing it over — three steps, and nobody leaves empty-handed:
1 write the file, and say where it went always
2 attach it, if this host can attach a file ask first — it sends a message
3 otherwise print the CSV into the chat so it can at least be copied
Step 2 is not available everywhere and must never be assumed. Do not silently skip to step 3 either: say which one you did.
investors.csv — UTF-8 with BOM| column | meaning |
|---|---|
id | stable row id — joins to sources.csv |
name | best-looking form |
type | the six values above |
investor_evidence | invests · operating_group · syndicate · unclear |
matches_request | yes · no · blank when undeterminable |
website | registrable domain as written |
headquarters | city, country |
headquarters_partial | yes when only the country is established |
person | the most senior individual you actually saw |
role | their title as the page states it |
people_known | how many named people you saw — one row is one firm, not one person |
linkedin | profile or company URL |
sectors | as the firm describes them |
fold_conflict | the other name this row's key collided with |
formerly | the firm's previous name, where a page states one |
type_blanked_reason | contradicts_request — the only value; empty otherwise |
part_of | the parent, where a page stated one |
sovereign_parent | the sovereign fund above it, where there is one |
key_quote | one sentence that evidences this firm is what the row says it is |
key_quote_url | the page that sentence is on |
checked | ISO date |
provenance | one of the five labels |
Illustrative — invented firms, to show the shape, not a run's output:
id,name,type,investor_evidence,matches_request,website,headquarters,headquarters_partial,...
f-001,Nadira Capital Partners,family_office,invests,yes,nadiracapital.example,"Lisbon, Portugal",,
f-002,Orsett Group,unknown,operating_group,,orsett.example,Portugal,yes,
The request named a type, so f-001 — a match — carries yes. f-002 shipped as unknown,
so whether it matches the named type could not be determined, which is the one case the
column's blank is for; a stated type that simply differs would carry no. Its
investor_evidence is still operating_group — you can see that a firm runs its own
businesses and still be unable to type it. f-001 was sourced to a city, f-002 only to a
country, hence headquarters_partial. Neither row would exist without a sources.csv row per
filled field.
key_quote must evidence the row, not merely come from the page. A sentence defining
what a family office is in general evidences nothing, and a reader scanning the file reads
a native-language quote as proof. No evidencing sentence → leave it empty and say so.
"Original language" means the language of the page you read, not of the country.
sources.csv — one row per citationid,field,rung,url
f-018,website,1,https://example.com/about
f-018,headquarters,2,https://registry.example/entry?id=7&x=1
A field may carry citations at more than one rung — that is good, not a problem. It does
mean "rows resting on a rung-4 source alone" is a NOT EXISTS question (no citation at
rung 1–3 for that field), never WHERE rung = 4.
A separate file, not a packed cell. Every in-cell format tried broke (why.md), and one
row per citation carries the rung as a real column.
investors-pending.csv — offered, not resolvedname,reason,note,first_seen,source_url
| reason | meaning | resolvable? |
|---|---|---|
budget | never got to it | yes |
sourceUnchecked | page exists, could not be fetched | yes |
noHeadquarters | HQ not established yet — two headquarters is not this, see Job 2 | yes |
unresolvedJob0 | Job 0 could not be decided even as unclear — you never found enough of the firm to judge | yes |
outOfCountry | real, wrong market — including a local fund whose manager is abroad; say which in note | no — terminal |
outOfScopeSovereign | sovereign or state parent | no — terminal |
namedFamilyNotFirm | a capital pool named only as "the X family" | no — terminal unless a vehicle name turns up |
individualNotFirm | a named angel with no vehicle — a real investor, but one row is one firm | no — terminal |
individualNotFirm is not a rejection. Individual angels are legitimate targets; they
simply do not fit a row that means "one firm". Keep them here with their source so the reader
can take them.
Only the resolvable reasons hold Gate 1 shut.
investors-rejected.csv — local working file, never publishedname,reason,evidence,checked,source_url
adviserNotInvestor · notAnInvestor · sourceDead · unsourceable.
Two of these four are permanent and two are not. adviserNotInvestor and
notAnInvestor describe the firm, so never search them again. sourceDead and
unsourceable describe one page on one day — re-offer them on a later run, because a
different route may source them properly. They are in this file so you can see the call was
made, not to suppress the name forever.
Keep the names, keep the file local. Shipping a result set to a client means
investors.csv plus a count of rejections, never the list.
rounds.csv — what was triedround,query,surface,offered,survived,dry_streak
rounds.csv is Gate 2's entire input, and it tells the next run which surfaces were already
tried — so you can start with one it did not.
ledger.txt — the compact memoryWritten at the end of every run, beside the others where files work, and printed in its own fenced block where they do not — after the CSV, because the user copies only this.
One job: stop the next run rediscovering what this one already decided. Four fields per firm — fold key, domain, decision, and a display name only for firms that were kept or are still open; rejected and adviser rows travel as key and domain, never as a name.
It never speaks about completeness. No round counts, no dry-surface tallies, nothing that could license the exhaustion sentence — see Gate 0.
Format, what may be trusted in a pasted one, the rule for scope changes, and how to print a
long CSV into a chat in numbered parts: reference/environment.md.
<MARKET> · 33 firms over 10 rounds + 4 verification passes
STOPPED ON BUDGET — PARTIAL, not an exhaustion count
written /home/u/work/investor-search/poland/ · 6 files · index.md updated
surfaces web search · commercial register · 2 association lists
NOT tried: news/deal announcements
offered 99 names — 33 survived
in English 89 offered · 33 survived (37%)
in <local> 10 offered · 0 survived (0%)
already held 14 names already on file — dropped
already ret'd 6 names this run had already returned — dropped
rejected 13 advisers, not investors
4 not investors · 2 source dead · 1 unsourceable
26 parked, unresolved
(33 + 14 + 6 + 13 + 4 + 2 + 1 + 26 = 99 — every offered name has one home)
type 5 family_office · 9 investment_group · 10 venture_capital
7 private_equity · 2 angel · 0 unknown
evidence 24 invests · 7 operating_group · 0 syndicate · 2 unclear
sources 173 citations · rung 1: 160 · rung 4: 9
quotes 25 of 33 rows carry an evidencing quote; 8 correctly have none
flags 7 fold_conflict · 8 rows whose HQ rests on a rung-4 source alone
(Shape only. The figures are illustrative — no shipped run has legitimately reached the
exhaustion sentence.)
Every offered name lands on exactly one line, and the lines sum to the offered count.
Not a slogan — a check. A kept row counts as survived even where one of its fields rests on
sourceUnchecked; the parked line counts only names that never became rows. A name that was offered and is not on any line was dropped without a
decision, which is the bucket with a hole. The two dedupe lines stay separate: "20 already
known or already returned" welds two failures with opposite fixes, and a run past round 1
always has some of both. Rejections are broken out by reason for the same reason — sourceDead
and unsourceable describe a page and may be re-offered later; adviserNotInvestor and
notAnInvestor describe the firm and never will.
Split the offered count by language — that line is what caught the language rule being
wrong. Report every flag. fold_conflict, operating_group, unclear and rung-4-only
rows are exactly what a reader must look at.
Where nothing could be written, the same report takes the same shape, with two lines replaced and no path:
NO FILES WRITTEN — chat output only. PARTIAL; exhaustion cannot be claimed here.
written nothing — chat only; exhaustion cannot be claimed
The counts are reported exactly as they would be on disk. Unresolved names are listed in the
ledger below as pending, not carried in a file.
The written line is not decoration — it is the only place the user learns where their
files actually are, and whether the completeness claim was available at all.
Say how you knew it was finished — or say plainly that you did not.
checked date on each row.