Search: a brief to a closed set¶
"Experts who held a VP-level Sales role in DACH and passed a compliance screening" is not
a ranking question. It names a closed set: every entity for which the store holds rows
satisfying each condition. POST /v1/public/search (over MCP, limma_search) takes that
request — as a plain-language brief, or as the constraints it compiles to — runs it
server-side over the relations, and returns the set with the denominator
that makes these are all of them sayable, or names why it is not.
Five stages, each visible in the response:
- compile — a
briefbecomes constraints, each carrying the words of the brief it came from (one schema-constrained model call; skipped when you sendconstraints); - expansion — each constraint through the workspace vocabulary;
- enumeration — every relation the constraints name, read over the same scoped
readers as
limma_facts, intersected per entity; - ranking — deterministic, over slots, no model;
- the warrant — matches with their rows and receipts, counts per constraint, None, Not recorded, the gaps, the printed expansion, and the completeness licence.
A brief may also name a condition no relation can express — twenty years marketing baby
formula in European markets. That part goes in criteria and is read from each
candidate's own documents, with the sentences that say so: criteria.
A question about an idea rather than a thing — where do we discuss pricing risk? —
goes in idea, and is answered over the parts of the documents: ideas.
The examples run against roster(client): five people, six held
roles from a warehouse export, and three compliance screenings. Every name is invented.
Sending constraints¶
A constraint is {relation, slot, op, value, brief_span, required} — one condition on one
slot of one relation, in the relation grammar. This needs no model:
from docs.examples.roster import roster
ws = roster(client).workspace_id
BRIEF = [
{"relation": "held_role", "slot": "seniority", "op": "gte", "value": "VP"},
{"relation": "held_role", "slot": "function", "op": "eq", "value": "Sales"},
{"relation": "held_role", "slot": "geography", "op": "eq", "value": "DACH"},
{"relation": "screening", "slot": "kind", "op": "eq", "value": "compliance"},
{"relation": "screening", "slot": "outcome", "op": "eq", "value": "passed"},
]
r = client.post("/v1/public/search", json={"workspace_id": ws, "constraints": BRIEF})
body = r.json()
print("bound:", body["bound"], "| ranking:", body["ranking"])
print("matched:", body["entities"]["matched"])
counts = {k: v for k, v in body["counts"].items() if k != "per_constraint"}
print("counts:", counts)
print("entities:", {k: v for k, v in body["entities"].items() if k != "matched"})
print("coverage:", body["coverage"])
bound: True | ranking: deterministic: optional-groups, threshold-distance, entity_id
matched: ['halvard_quist']
counts: {'universe': 5, 'matched': 1, 'none': 3, 'not_recorded': 1, 'undetermined': 0, 'bound': 4}
entities: {'none': ['isolde_marbrook', 'odile_fenwright', 'teodor_brask'], 'not_recorded': ['yusuf_alderane'], 'undetermined': []}
coverage: {'completeness_licensed': False, 'withheld_because': 'narrowed', 'narrowed_by': ['entities_not_recorded']}
One entity satisfies every condition. The other four are not a single "no": three are none and one is not recorded, and the difference is the whole point of the verb.
Reading the warrant¶
The partition¶
Every entity the relation space knows (counts.universe, entities of the relations'
subject kind in your scope) is in exactly one of four sets:
| set | meaning | licenses |
|---|---|---|
matched |
a row satisfies every required condition | this entity matches, with the rows to show it |
none |
some required condition's relation was looked at for the entity and no row satisfies it | no recorded row satisfies this |
not_recorded |
some required relation was never looked at for the entity (and none of the above) | NOTHING — never "does not match", never "clean" |
undetermined |
its only candidate rows cannot be judged (see as_of, an unrecorded slot and corrections) | nothing either way |
counts.bound is matched + none: the entities whose membership is determined.
Conditions on the same relation are grouped, and a group is one row's conditions:
"VP-level", "Sales" and "DACH" must hold on ONE held role, not on three different roles.
groups shows each group as it ran; none / not_recorded / undetermined list the
entity ids per group:
from docs.examples.roster import roster
ws = roster(client).workspace_id
BRIEF = [
{"relation": "held_role", "slot": "seniority", "op": "gte", "value": "VP"},
{"relation": "held_role", "slot": "function", "op": "eq", "value": "Sales"},
{"relation": "held_role", "slot": "geography", "op": "eq", "value": "DACH"},
{"relation": "screening", "slot": "kind", "op": "eq", "value": "compliance"},
{"relation": "screening", "slot": "outcome", "op": "eq", "value": "passed"},
]
body = client.post("/v1/public/search",
json={"workspace_id": ws, "constraints": BRIEF}).json()
for group in body["groups"]:
print("group", group["index"], group["relation"], "constraints", group["constraints"],
"| covered:", group["covered"], "| matched:", group["matched"],
"| none:", group["none"], "| not_recorded:", group["not_recorded"])
for listing in ("none", "not_recorded"):
for entry in body[listing]:
print(listing, "| group", entry["group"], entry["relation"], "->", entry["entity_ids"])
group 0 held_role constraints [0, 1, 2] | covered: 5 | matched: 3 | none: 2 | not_recorded: 0
group 1 screening constraints [3, 4] | covered: 4 | matched: 2 | none: 2 | not_recorded: 1
none | group 0 held_role -> ['odile_fenwright', 'teodor_brask']
none | group 1 screening -> ['isolde_marbrook', 'teodor_brask']
not_recorded | group 0 held_role -> []
not_recorded | group 1 screening -> ['yusuf_alderane']
Read one person at a time. Isolde holds a VP-level Sales role in Switzerland but her
compliance screening failed: the screening group looked at her and found no passing row,
so she is none. Teodor is none on both groups — a C-level Operations role in the
United States, and (the roles export says it checked) no screening at all. Yusuf holds a
VP Sales role in Austria, and nothing ever looked at his screenings: he is not
recorded, and the licence is withheld (narrowed_by: entities_not_recorded) because he
may well have passed one.
Absence inside a group¶
Each group carries the relation's per-entity absence verdict — the same block
limma_facts returns (None versus Not recorded).
absence.none names the entities holding NO row of the relation at all, each with the
coverage row that says a load looked; it is stronger than the group's none, which also
counts entities whose rows exist and do not satisfy the condition:
from docs.examples.roster import roster
ws = roster(client).workspace_id
body = client.post("/v1/public/search", json={"workspace_id": ws, "constraints": [
{"relation": "screening", "slot": "outcome", "op": "eq", "value": "passed"}]}).json()
group = body["groups"][0]
print("group none:", body["none"][0]["entity_ids"])
absence = group["absence"]
print("split:", absence["split"])
for verdict in absence["none"] + absence["not_recorded"]:
print(verdict["entity_id"], "| kind:", verdict["kind"], "| denominator:",
verdict["denominator"], "| declared_by:",
[c["declared_by"] for c in verdict["coverage"]])
group none: ['isolde_marbrook', 'teodor_brask']
split: {'none': 1, 'not_recorded': 1, 'recorded': 3}
teodor_brask | kind: none | denominator: 0 | declared_by: ['source']
yusuf_alderane | kind: not_recorded | denominator: None | declared_by: []
Isolde is in the group's none (her one screening failed) but not in absence.none (a
screening row exists). Teodor is in both.
Counts per constraint¶
counts.per_constraint[i] counts constraint i ALONE, over the same readers — which
condition is doing the narrowing:
from docs.examples.roster import roster
ws = roster(client).workspace_id
BRIEF = [
{"relation": "held_role", "slot": "seniority", "op": "gte", "value": "VP"},
{"relation": "held_role", "slot": "function", "op": "eq", "value": "Sales"},
{"relation": "held_role", "slot": "geography", "op": "eq", "value": "DACH"},
{"relation": "screening", "slot": "kind", "op": "eq", "value": "compliance"},
{"relation": "screening", "slot": "outcome", "op": "eq", "value": "passed"},
]
body = client.post("/v1/public/search",
json={"workspace_id": ws, "constraints": BRIEF}).json()
for entry in body["counts"]["per_constraint"]:
c = entry["constraint"]
print(entry["index"], c["slot"], c["op"], c["value"], "|", entry["status"],
"| matched", entry["matched"], "| rows", entry["rows"], "| none", entry["none"],
"| not_recorded", entry["not_recorded"])
print("expansion:")
for line in body["expansion"]:
print(" ", line)
0 seniority gte VP | bound | matched 4 | rows 4 | none 1 | not_recorded 0
1 function eq Sales | bound | matched 3 | rows 4 | none 2 | not_recorded 0
2 geography eq DACH | bound | matched 4 | rows 5 | none 1 | not_recorded 0
3 kind eq compliance | bound | matched 3 | rows 3 | none 1 | not_recorded 1
4 outcome eq passed | bound | matched 2 | rows 2 | none 2 | not_recorded 1
expansion:
seniority: ≥ VP → {VP, SVP, C-level} via order
function: Sales is a term of axis function
geography: DACH → {DACH, Germany, Austria, Switzerland} via hierarchy
kind: compliance is a term of axis kind
outcome: passed is a term of axis outcome
rows is the number of current rows satisfying that one condition; matched the entities
holding one. An unbound constraint (below) carries nulls.
Bounding the lists¶
The lists of people who did NOT match grow with the directory, and an agent that reports
numbers rarely needs them whole. list_limit (0–200) bounds every list of non-matches —
the none / not_recorded / undetermined ids, per group and overall, and a group's
absence entries. It never touches the matches, and the counts stay whole:
from docs.examples.roster import roster
ws = roster(client).workspace_id
passed = [{"relation": "screening", "slot": "outcome", "op": "eq", "value": "passed"}]
whole = client.post("/v1/public/search", json={"workspace_id": ws, "constraints": passed}).json()
bounded = client.post("/v1/public/search", json={"workspace_id": ws, "constraints": passed,
"list_limit": 0}).json()
print("unbounded:", whole["entities"]["none"], whole["entities"]["not_recorded"],
"| list_limit in the response:", "list_limit" in whole)
print("bounded: ", bounded["entities"]["none"], bounded["entities"]["not_recorded"],
"| list_limit:", bounded["list_limit"])
print("matches, both:", whole["entities"]["matched"] == bounded["entities"]["matched"],
"| counts, both:", whole["counts"] == bounded["counts"],
"| none, counted:", bounded["none"][0]["total"])
unbounded: ['isolde_marbrook', 'teodor_brask'] ['yusuf_alderane'] | list_limit in the response: False
bounded: [] [] | list_limit: 0
matches, both: True | counts, both: True | none, counted: 2
Unset, each such list carries up to 200 ids. Send 10 to show a few, 0 to report numbers
only — and take the numbers from counts and each list's total, never from the length
of a list you asked to be cut. The same bound applies to a criterion's lists
(criteria).
The compact reply¶
The warrant above is what an integrator's code reads. An agent holds the reply in its
context and re-reads it on every turn, and most of the warrant is evidence it will not
quote: every row's sources and receipts, up to 200 ids per list of non-matches. detail:
"compact" answers the same search as the matches, one line per condition on why each is
in the set, and counts of everything else — a projection of the full reply, sized by the
matches returned and never by the candidates examined. The HTTP verb's default stays
"full"; the MCP tool limma_search defaults to "compact". No key of it names a
domain: a workspace may hold people, companies, contracts or products, and an entry is
a match (matches, close.matches) whatever it holds.
from docs.examples.roster import roster
ws = roster(client).workspace_id
passed = [{"relation": "screening", "slot": "outcome", "op": "eq", "value": "passed"}]
reply = client.post("/v1/public/search", json={"workspace_id": ws, "constraints": passed,
"detail": "compact", "limit": 1}).json()
print(sorted(reply))
print("matched:", reply["matched"], "| complete:", reply["complete"],
reply.get("incomplete_because"))
for match in reply["matches"]:
print(match["entity_id"], match["name"], match["why"])
print(" about:", match.get("about"))
print("others:", reply["others"])
page_2 = client.post("/v1/public/search", json={
"workspace_id": ws, "constraints": passed, "detail": "compact", "limit": 1,
"offset": reply["matched"]["next_offset"]}).json()
print("next page:", [p["entity_id"] for p in page_2["matches"]], page_2["matched"])
['close', 'complete', 'detail', 'incomplete_because', 'map_version', 'matched', 'matches', 'more', 'others', 'request_id']
matched: {'total': 2, 'showing': [1, 1], 'next_offset': 1} | complete: False ['entities_not_recorded: 1']
halvard_quist None [{'condition': 'screening.outcome = passed', 'value': 'passed', 'from': 'record'}]
about: ['Austria, Director, Sales, Lumen Ridge (2019–now)', 'compliance, passed, as of 2024-02', 'Germany, VP, Vice President, Sales, Kestrel Pumps AG (2016–2018)']
others: {'ruled_out': 2, 'cannot_tell': 0, 'not_read': 1, 'no_documents': 0}
next page: ['odile_fenwright'] {'total': 2, 'showing': [2, 2], 'next_offset': None}
matched.totaliscounts.matched— the whole set, whatever the page. Page withoffset(next_offset; null on the last page);limitdefaults to 25 here.completeiscoverage.completeness_licensed, andincomplete_becausethe licence's own reasons, at most five, each with the count the full reply holds for it (here one person no load ever screened: nobody can say she did not pass). A page of a set nothing else narrows saystruncated, with the number of matches not in this reply.matches[].whyis one line per condition a delivered row establishes:condition(the constraint asrelation.slot op value, or a criterion's own words for a facet),value(what the row or the document states),from—recordfor a row loaded as stated,textfor one read from a document (with the supportingquote, at most 160 characters, where it says more thanvalue),computedfor a length taken from a record's dates — andcheck, present only when the source no longer reads the value.nameis the display name the workspace recorded for the entity — what a document load'sname_pathread for their record, whether or not the load registered a document about them — and null where it holds none (this roster was loaded from SQL tables, which record none): it is never derived from an id. The full reply'smatches[].nameis the same field.aboutsays who a listed match is, whatever was asked: at most three lines (120 characters each) of what the workspace's own relations hold for it now — the row of each relation it holds most recently: a role first (whether or not it states its years), then a standing fact such as a place, then a role that ended — one line a row, in the row's own values; a term its vocabulary holds is said by the vocabulary's own label (a record that writes "Deutschland" reads "Germany", as the search matched it). It is read in one batch for the entities the reply lists, from the relations the packs declare and in the caller's document scope; an entity the workspace holds nothing for has noabout. The entries ofclose.matchescarry it too.whyanswers "why is this one in the set";aboutanswers "who is this".othersare counts only:ruled_out(counts.none— the one "no"),cannot_tell(counts.undetermined, less the close matches),no_documentsandnot_read(togethercounts.not_recorded).close.matcheslists a criterion's candidates with exactly one condition open — nothing stated for it (because: "not_stated"), or stated in a sentence the reader did not take as proved (because: "stated_not_verified", with that sentence asquote) — whose own documents establish a condition of SUBSTANCE: a condition no vocabulary of places types (the block's facet says whichaxistypes it). Somebody who shares only a place, a length or a time with the question is counted undercannot_tell, never listed: criteria.candidates(with a criterion): how many profiles its niche is read for — everybody the ordinary conditions leave. It is what a question with a niche CHECKED;otherspartitions the whole directory.list_limitis not read: a compact reply lists no non-match.spentis present only when the call itself made model calls for a criterion —{calls, reads, cost_usd, cost_basis}, the criterion's own usage for this call (cost_usdnull means it could not be priced, never that it was free). A call served from stored judgments carries none.readingis present only while a criterion's read is unfinished. A criterion is read only while a call is open: the reads still running when a call returns finish behind it, and nothing new starts until the next call — so call again with thecriterionaddress at once; waiting reads nothing.map_version, last, is the workspace map's version as you may see it — the numberfactscarries inlabels.map_version, and the full reply'smap_version: it moves when a name your documents carry is added, re-spelled or decided, never because of a document you may not read. A version you hold that is still the reply's says the names you cached are still the map's. An idea search carries it too.
What it leaves out is fetched when it is wanted. more names the calls: more.entity,
the same search restricted to entities with entity_ids and detail: "full", returns
those entities' rows and receipts — and nothing about any other, with every count still
of the whole set (more.rows is one entity's rows of a relation through facts;
more.receipt a receipt through region).
(A search with a criterion names one more, criterion: the same call with entity_ids:
[] delivers NOBODY's rows — the full reply's criteria block with empty lists, which is
the criterion's own working: its facets, its decomposition and estimate, what the people
one condition short state instead.)
from docs.examples.roster import roster
ws = roster(client).workspace_id
passed = [{"relation": "screening", "slot": "outcome", "op": "eq", "value": "passed"}]
reply = client.post("/v1/public/search", json={"workspace_id": ws, "constraints": passed,
"detail": "compact"}).json()
print(sorted(reply["more"]))
person = reply["matches"][-1]["entity_id"]
detail = client.post("/v1/public/search", json={
"workspace_id": ws, "constraints": passed, "entity_ids": [person],
"detail": "full"}).json()
print([m["entity_id"] for m in detail["matches"]] == [person],
"| rank:", detail["matches"][0]["rank"], "| counts.matched:", detail["counts"]["matched"])
row = detail["matches"][0]["rows"][0]
print(row["relation"], row["slots"]["outcome"], "| receipt:",
{k: row["slot_receipts"]["outcome"][k] for k in ("db_table", "db_column", "byte_verified")})
['entity', 'full', 'receipt', 'rows']
True | rank: 2 | counts.matched: 2
screening passed | receipt: {'db_table': 'screenings', 'db_column': 'outcome', 'byte_verified': True}
request_id is the call's id: its audit event (GET /v1/audit/events?request_id=…)
records the facts and spans the answer rests on, though the reply did not carry them.
The rows and their receipts¶
matches[] is {entity_id, name, rank, rank_key, rows} in rank order, and rows is every
row that satisfied a group for that entity, each marked with the groups it satisfied:
from docs.examples.roster import roster
ws = roster(client).workspace_id
BRIEF = [
{"relation": "held_role", "slot": "seniority", "op": "gte", "value": "VP"},
{"relation": "held_role", "slot": "function", "op": "eq", "value": "Sales"},
{"relation": "held_role", "slot": "geography", "op": "eq", "value": "DACH"},
{"relation": "screening", "slot": "kind", "op": "eq", "value": "compliance"},
{"relation": "screening", "slot": "outcome", "op": "eq", "value": "passed"},
]
body = client.post("/v1/public/search",
json={"workspace_id": ws, "constraints": BRIEF}).json()
match = body["matches"][0]
print(match["entity_id"], "| name:", match["name"], "| rank:", match["rank"])
for row in match["rows"]:
print(" ", row["relation"], row["slots"], "| satisfies groups", row["satisfies_groups"])
print(" provenance_kind:", row["provenance_kind"], "| receipt:", row["receipt"],
"| slot receipts:", sorted((s, v["db_table"], v["db_column"], v["byte_verified"])
for s, v in row["slot_receipts"].items()))
print("provenance split over delivered rows:", body["provenance"])
halvard_quist | name: None | rank: 1
held_role {'company': 'Kestrel Pumps AG', 'function': 'Sales', 'geography': 'Germany', 'seniority': 'VP', 'title': 'Vice President, Sales'} | satisfies groups [0]
provenance_kind: declared | receipt: None | slot receipts: [('company', 'roles', 'company', True), ('function', 'roles', 'function', True), ('geography', 'roles', 'geography', True), ('seniority', 'roles', 'seniority', True), ('title', 'roles', 'title', True)]
screening {'as_of': '2024-02', 'kind': 'compliance', 'outcome': 'passed'} | satisfies groups [1]
provenance_kind: declared | receipt: None | slot receipts: [('as_of', 'screenings', 'screened_on', True), ('kind', 'screenings', 'kind', True), ('outcome', 'screenings', 'outcome', True)]
provenance split over delivered rows: {'declared': 2, 'extracted': 0}
- A row loaded from a database has
receipt: nulland a structured receipt per slot, re-read from its cell on delivery; a row stated by a document has a spanreceiptthatlimma_regionre-reads (relations). provenanceis{declared, extracted}over the rows delivered inmatches: adeclaredrow came from a structured load, anextractedone was read from a document — a relation row a reader recovered at ingest, or the judgment a criterion wrote when it read a profile. This roster was loaded from a database export, so every row here is declared.nameisnull: the store holds no display name for a relation entity and never derives one from the id.
Gaps: what did not run¶
A constraint the workspace cannot express is unbound: it is listed with its reason, it does not run, and the search runs over the rest and withholds the licence naming it:
from docs.examples.roster import roster
ws = roster(client).workspace_id
body = client.post("/v1/public/search", json={"workspace_id": ws, "constraints": [
{"relation": "held_role", "slot": "function", "op": "eq", "value": "Sales"},
{"relation": "engagement", "slot": "topic", "op": "eq", "value": "pricing"},
{"relation": "board_seat", "slot": "company", "op": "eq", "value": "Sable Point"},
]}).json()
for gap in body["gaps"]["unbound"]:
print("unbound", gap["index"], "->", gap["reason"])
print("dropped:", body["gaps"]["dropped"], "| uncompiled:", body["gaps"]["uncompiled"])
print("matched:", body["entities"]["matched"])
print("coverage:", body["coverage"])
unbound 1 -> vocabulary_unbound:topic
unbound 2 -> relation_unknown:board_seat
dropped: [] | uncompiled: []
matched: ['halvard_quist', 'isolde_marbrook', 'yusuf_alderane']
coverage: {'completeness_licensed': False, 'withheld_because': 'narrowed', 'narrowed_by': ['constraint_unbound:1', 'constraint_unbound:2']}
vocabulary_unbound:<slot> is a term slot with no vocabulary (engagement topics are
open-ended in this pack); relation_unknown:<name> a relation no pack here declares;
slot_unknown:<name> a slot the relation does not declare. The three matches satisfy the
one condition that ran — not the request. Tell the user each gap: that part of the
request was not searched.
gaps.dropped and gaps.uncompiled are the compile's gaps and are empty on the
constraints path (a brief, below). A constraint that is malformed as a
request is a 422 on this path, never a gap — the request never made sense, and answering
part of it would read as an answer to the whole:
from docs.examples.roster import roster
ws = roster(client).workspace_id
for constraint in (
{"relation": "held_role", "slot": "seniority", "op": "in", "value": "VP"},
{"relation": "engagement", "slot": "hourly_rate_usd", "op": "gte", "value": "1,5"},
{"relation": "held_role", "slot": "company", "op": "gte", "value": "Kestrel"}):
r = client.post("/v1/public/search",
json={"workspace_id": ws, "constraints": [constraint]})
detail = r.json()["detail"]
print(r.status_code, detail["type"], "|", detail["title"])
r = client.post("/v1/public/search", json={"workspace_id": ws, "constraints": []})
print(r.status_code, r.json()["detail"]["title"])
422 search_constraint_invalid | constraints[0] is malformed
422 search_constraint_invalid | a constraint asks its slot something its type cannot answer
422 search_constraint_invalid | a constraint asks its slot something its type cannot answer
422 `constraints` is empty
Ranking¶
The order is deterministic: optional-groups, threshold-distance, entity_id — no model
ranks anything:
- optional groups satisfied, most first.
required: falsemakes a condition a PREFERENCE: it never removes an entity from the set and never withholds the licence; it moves the entities that satisfy it up. - distance from each
gte/ltethreshold, nearest first — one key per such constraint in request order, so months and dollars are never summed; on an ordered term axis the distance is in rungs. entity_id, ascending.
matches[].rank_key shows the key each entity was ranked by:
from docs.examples.roster import roster
ws = roster(client).workspace_id
preference = client.post("/v1/public/search", json={"workspace_id": ws, "constraints": [
{"relation": "held_role", "slot": "function", "op": "eq", "value": "Sales"},
{"relation": "screening", "slot": "outcome", "op": "eq", "value": "passed",
"required": False},
]}).json()
print("preference:", [(m["rank"], m["entity_id"], m["rank_key"]["optional_groups_satisfied"])
for m in preference["matches"]],
"| licensed:", preference["coverage"]["completeness_licensed"])
threshold = client.post("/v1/public/search", json={"workspace_id": ws, "constraints": [
{"relation": "held_role", "slot": "seniority", "op": "gte", "value": "Director"}]}).json()
print("threshold:", [(m["rank"], m["entity_id"], m["rank_key"]["threshold_distance"])
for m in threshold["matches"]])
preference: [(1, 'halvard_quist', 1), (2, 'isolde_marbrook', 0), (3, 'yusuf_alderane', 0)] | licensed: True
threshold: [(1, 'halvard_quist', [0.0]), (2, 'odile_fenwright', [0.0]), (3, 'yusuf_alderane', [1.0]), (4, 'isolde_marbrook', [2.0]), (5, 'teodor_brask', [3.0])]
Halvard ranks first under the preference because his screening passed; Isolde's failed
and Yusuf's was never recorded, but neither leaves the set, and the licence holds — a
preference is not a condition. Under ≥ Director, Halvard and Odile hold a Director role
(distance 0) and tie, broken by id; Teodor's C-level role is three rungs above.
limit (default 50, at most 500) caps matches only. The counts and entities.matched
(ids in rank order, up to 200) always describe the whole set, and a page is not a
complete answer:
from docs.examples.roster import roster
ws = roster(client).workspace_id
body = client.post("/v1/public/search", json={"workspace_id": ws, "limit": 1, "constraints": [
{"relation": "held_role", "slot": "seniority", "op": "eq", "value": "VP"}]}).json()
print("matches on the page:", [m["entity_id"] for m in body["matches"]],
"| truncated:", body["truncated"])
print("every match:", body["entities"]["matched"], "| counts.matched:", body["counts"]["matched"])
print(body["coverage"])
matches on the page: ['halvard_quist'] | truncated: True
every match: ['halvard_quist', 'yusuf_alderane'] | counts.matched: 2
{'completeness_licensed': False, 'withheld_because': 'truncated'}
as_of¶
as_of (YYYY-MM) asks the question at a month. A dated row counts when its
half-open period contains the month: period_from ≤ as_of and (period_to open or
period_to > as_of). Without as_of, every current row counts whatever its period.
from docs.examples.roster import roster
ws = roster(client).workspace_id
VP_IN_DACH = [
{"relation": "held_role", "slot": "seniority", "op": "gte", "value": "VP"},
{"relation": "held_role", "slot": "geography", "op": "eq", "value": "DACH"},
]
for month in (None, "2018-01", "2020-01"):
body = client.post("/v1/public/search", json={
"workspace_id": ws, "constraints": VP_IN_DACH,
**({"as_of": month} if month else {})}).json()
group = body["groups"][0]
print(month, "->", body["entities"]["matched"], "| active rows:", group["active_rows"],
"| inactive rows:", group["inactive_rows"], "| as_of_source:", body["as_of_source"])
print(body["groups"][0]["time_test"]["rule"])
None -> ['halvard_quist', 'yusuf_alderane', 'isolde_marbrook'] | active rows: 3 | inactive rows: 0 | as_of_source: None
2018-01 -> ['halvard_quist'] | active rows: 1 | inactive rows: 2 | as_of_source: request
2020-01 -> ['isolde_marbrook'] | active rows: 1 | inactive rows: 2 | as_of_source: request
dated rows: period_from ≤ 2020-01 and (period_to open or period_to > 2020-01); undated rows cannot be placed at 2020-01: the relation declares no date slot — unanswerable
In January 2018 Halvard held his VP role at Kestrel; Isolde's SVP role began in June. By January 2020 Halvard's VP role had ended (he is none — his open role is a Director one), Isolde's was held, and Yusuf's began in April.
An undated row (a screening, dated by its own as_of slot) makes no tenure claim. It
counts at as_of when it is current and the relation's ONE date slot names a date whose
whole span ends by the end of the month. The rule is stated per group in time_test:
from docs.examples.roster import roster
ws = roster(client).workspace_id
body = client.post("/v1/public/search", json={"workspace_id": ws, "as_of": "2024-01",
"constraints": [{"relation": "screening", "slot": "outcome", "op": "eq",
"value": "passed"}]}).json()
group = body["groups"][0]
print("matched:", body["entities"]["matched"], "| none:", body["entities"]["none"])
print("undated rows:", group["undated_rows"], "| active:", group["active_rows"],
"| inactive:", group["inactive_rows"], "| date slot:", group["time_test"]["date_slot"])
print(group["time_test"]["rule"])
matched: ['odile_fenwright'] | none: ['halvard_quist', 'isolde_marbrook', 'teodor_brask']
undated rows: 2 | active: 1 | inactive: 1 | date slot: as_of
dated rows: period_from ≤ 2024-01 and (period_to open or period_to > 2024-01); undated rows: current, and the whole of as_of ≤ 2024-01 (a as_of whose span straddles 2024-01, such as a bare year, is unanswerable)
Odile's screening (November 2023) had happened by January 2024; Halvard's (February 2024)
had not, so at that month he is none. A row that cannot be placed — its relation declares
no date slot or two, the value is missing, or it names a span that straddles the month (a
bare year "2024" at 2024-01) — is unanswerable: an entity whose only candidate rows
are unanswerable is undetermined, never none, and the licence is withheld with
undated_rows_unanswerable:<relation>. The same holds for a period_from / period_to
constraint over a row whose column is empty (open_period_rows_unanswerable:<relation>).
An unrecorded slot¶
A pack can declare a slot no load wrote: a held_role.industry over roles whose export
carried no industry column. Such a role is neither in Technology nor out of it, so a row
that holds NO value for a slot a constraint names — and fails no other condition of its
group — cannot be judged: its entity is undetermined unless another of its rows matches,
groups[].unrecorded_slots names the slot, and the licence is withheld with
slot_unrecorded_rows_unanswerable:<relation>. industry eq Technology and industry ne
Technology over such rows are therefore never both licensed empty. A row another condition
fails on the record (an Operations role under function eq Sales) is still a non-match. The
facts verb reads the same rows the same way (relations.slot_unrecorded,
narrowed_by: slot_unrecorded_rows).
as_of on the search verb is a month the ROWS are tested at. It does not select a
document generation: every count is over the current generation of each source (see
supersession).
A brief¶
brief is the request in plain language, compiled server-side by ONE schema-constrained
model call. Send exactly one of brief and constraints (both, or neither, is a 422). A
deployment with no model answers a brief with 503 and says what to do instead:
from docs.examples.roster import roster
ws = roster(client).workspace_id
r = client.post("/v1/public/search", json={
"workspace_id": ws, "brief": "VP-level Sales leaders in DACH."})
print(r.status_code, r.json())
r = client.post("/v1/public/search", json={
"workspace_id": ws, "brief": "VP-level Sales leaders in DACH.",
"constraints": [{"relation": "held_role", "slot": "seniority", "op": "gte",
"value": "VP"}]})
print(r.status_code, r.json()["detail"]["title"])
503 {'type': 'compile_unavailable', 'reason': 'compile requires a model; pass constraints'}
422 send exactly one of `brief` and `constraints`
Over MCP the same refusal reaches the agent as a tool error (isError: true) whose text is
the verb's own body as JSON — {status, path, detail}, with detail exactly as the route
wrote it — after the MCP library's Error executing tool limma_search: prefix. Match on
detail.type, not on the wording. The call below goes through an MCP client session to the
real tool (call_tool):
import json
from docs.examples.over_mcp import call_tool
from docs.examples.roster import roster
ws = roster(client).workspace_id
result = call_tool(client, "limma_search",
{"workspace_id": ws, "brief": "VP-level Sales leaders in DACH."})
prefix, _, body = result.content[0].text.partition(": ")
refusal = json.loads(body)
print("isError:", result.isError, "|", prefix)
print(refusal["status"], refusal["path"], refusal["detail"])
isError: True | Error executing tool limma_search
503 /v1/public/search {'type': 'compile_unavailable', 'reason': 'compile requires a model; pass constraints'}
There is no rule-based fallback: a guessed compile would be an answer to a different question.
What the compile does¶
The model is forced to call one tool whose input schema IS the workspace's address space:
one variant per (relation, slot), the ops that slot's type supports (gte / lte on a
term slot only where its axis is ordered), the axis's terms in the slot description, and
brief_span + quote required on every constraint:
from epistem.bundles.catalogue_loader import parse_relation_sections
from epistem.bundles.registry import load_registry_bundle
from epistem.substrate.search import compile_tool
pack = parse_relation_sections(load_registry_bundle("bundle_expert_network"))
tool = compile_tool(pack.schema, pack.vocabulary)
variants = tool["input_schema"]["properties"]["constraints"]["items"]["anyOf"]
print(tool["name"], "|", len(variants), "relation.slot variants")
for variant in variants:
p = variant["properties"]
if p["slot"]["enum"][0] in ("seniority", "title"):
print(p["op"]["enum"], "|", p["slot"]["description"])
print("required:", variants[0]["required"])
compile_search_brief | 13 relation.slot variants
['eq', 'ne', 'in', 'gte', 'lte'] | held_role.seniority (term): axis seniority, ordered low to high: IC < Manager < Director < VP < SVP < C-level. Write the term, or the brief's own word when it is a synonym — synonyms and groups are resolved server-side
['eq', 'ne', 'in'] | held_role.title (term): axis title has NO vocabulary in this workspace; a constraint here is reported unbound and does not run
required: ['relation', 'slot', 'op', 'value', 'brief_span', 'quote', 'required']
What the model returns is then checked against the brief, deterministically, before
anything runs. The example below hands that checker — read_compile_output, the function
the route runs on the model's tool call — a compile with two faults: one constraint whose
offsets are mis-counted but whose quote is text of the brief, and one whose quote the brief
does not contain. The third sentence of the brief states nothing the pack can express:
from epistem.bundles.catalogue_loader import parse_relation_sections
from epistem.bundles.registry import load_registry_bundle
from epistem.substrate.search import read_compile_output
pack = parse_relation_sections(load_registry_bundle("bundle_expert_network"))
brief = ("Find VP-level Sales leaders in DACH. They must have passed a compliance "
"screening. Fluent in German.")
def at(words):
start = brief.index(words)
return [start, start + len(words)]
model_said = {"constraints": [
{"relation": "held_role", "slot": "seniority", "op": "gte", "value": "VP",
"quote": "VP-level", "brief_span": at("VP-level"), "required": True},
{"relation": "held_role", "slot": "function", "op": "eq", "value": "Sales",
"quote": "Sales leaders", "brief_span": [0, 3], "required": True},
{"relation": "held_role", "slot": "geography", "op": "eq", "value": "DACH",
"quote": "DACH", "brief_span": at("DACH"), "required": True},
{"relation": "screening", "slot": "outcome", "op": "eq", "value": "passed",
"quote": "passed", "brief_span": at("passed"), "required": True},
{"relation": "screening", "slot": "kind", "op": "eq", "value": "compliance",
"quote": "a compliance check", "brief_span": [0, 5], "required": True},
]}
compiled = read_compile_output(brief, pack.schema, pack.vocabulary, model_said)
for c in compiled.constraints:
moved = f" (relocated from {c.span_relocated_from})" if c.span_relocated_from else ""
print("kept", c.slot, c.op, c.value, c.brief_span, repr(c.quote) + moved)
for d in compiled.dropped:
print("dropped", d["constraint"]["slot"], "->", d["reason"])
print("uncompiled:", compiled.uncompiled)
kept seniority gte VP (5, 13) 'VP-level'
kept function eq Sales (14, 27) 'Sales leaders' (relocated from (0, 3))
kept geography eq DACH (31, 35) 'DACH'
kept outcome eq passed (52, 58) 'passed'
dropped kind -> quote_not_in_brief
uncompiled: [{'span': [83, 100], 'text': 'Fluent in German.'}]
The rules, from epistem/substrate/search.py:
- a constraint must be attributable to whole words of the brief. A span that does not
re-slice to its quote is re-located when the quote occurs exactly once as whole words
(
span_relocated_fromnames the offsets the model gave); a quote that occurs more than once, with a span on none of the occurrences, is dropped asspan_ambiguous; one found only inside longer words isquote_not_whole_words; one found only across a sentence break isquote_crosses_sentences(it would mark the next sentence compiled while holding none of its condition); one not in the brief at all isquote_not_in_brief— the constraint came from nowhere; a blank quote isquote_empty; - a constraint naming a relation, slot or op the tool did not offer, or asking a slot something its type cannot answer, is dropped with the reason;
- every sentence no kept constraint came from is listed as
uncompiled— that part of the request was NOT searched.
On the brief path the response carries the compile under compiled (brief,
constraints with brief_span and quote, dropped, uncompiled, as_of, model,
usage); gaps.dropped and gaps.uncompiled repeat its gaps, and each withholds the
licence (narrowed_by: constraints_dropped, brief_uncompiled). Check the compile read
the request the way it was meant — and if it did not, send the corrected constraints
back: compiled.constraints is the shape the constraints field takes. A brief that pins
a month ("as of March 2020") compiles to compiled.as_of; an as_of in the request wins
over it, and as_of_source says which applied (request or compiled).
A workspace that declares no relation answers a brief without calling a model: every
sentence is uncompiled and unbound_because is workspace_declares_no_relation.
What the licence covers¶
coverage.completeness_licensed: true licenses every entity satisfying the request, as
run, is in entities.matched — zero included, which is then there are none. When it is
false, narrowed_by names every reason, and each is a different instruction:
narrowed_by |
what it means |
|---|---|
acl |
your key reads only some of the workspace's documents |
constraints_dropped / brief_uncompiled |
part of a brief did not compile (a brief) |
constraint_unbound:<i> |
constraint i could not run (gaps) |
vocabulary_unknown_surface:<slot> / vocabulary_unplaced:<slot> |
the vocabulary could not vouch for a constraint's set (vocabulary) |
entities_not_recorded |
some entity was never looked at for a required relation |
relation_not_recorded:<relation> |
a required relation is declared and no load ever looked at anyone for it |
undated_rows_unanswerable:<relation> / open_period_rows_unanswerable:<relation> |
rows that cannot be placed at as_of (as_of) |
corrected_rows_unanswerable:<relation> |
an entity holds a row a correction retired that a document still states (corrections) |
withheld_rows_unanswerable:<relation> |
a connected export states a row for an entity that its loader could not read, and no readable row of theirs matches — they are undetermined, never none (a document-store export) |
unattributed_rows_withheld:<relation> |
a connected export holds a document its loader could tie to no entity: whoever it was about is in no list, so these are all cannot be said of that relation until a re-sync states it readably |
slot_unrecorded_rows_unanswerable:<relation> |
a candidate row holds no value for a slot a constraint names (an unrecorded slot) |
withheld_because: truncated means only that matches is a page. None of this licenses
that a matched row's values are right: read each row's receipts, and retire a wrong row
with limma_correct.