Search
There are three separate search surfaces — they cover different tables and don't share a single index.
Statements and events — GET /suche
curl -sS "$COMPASS_URL/suche?q=timeout" -H "X-Compass-Key: $COMPASS_KEY_RO"
Full-text search (Postgres tsvector) over Aussagen and Ereignisse
together. The answer always names the total, not just the page:
{
"treffer": [{"typ": "aussage", "id": 4711, "text": "…", "sache": "…",
"status": "gilt", "gilt_seit": "…", "gilt_bis": null, "rang": 0.71}],
"gesamt": 290,
"abgeschnitten": true,
"weiter": 100
}
The MCP tool is suche(q, subjekt_id?, persona_slug?, alle=False, limit?, offset?); it adds a hinweis in words ("100 von 290 Treffern. Weiter mit offset=100.").
gesamt counts after the visibility filter, not before — the page you
hold and the number next to it describe the same set. abgeschnitten says
whether anything stands behind this page, and weiter names the offset
for the next one, or is null when there is nothing left. To get
everything, page until weiter is null — you never have to do the
arithmetic to find out whether you are done.
Changed 2026-10-05 (breaking). The answer used to be a list and was truncated silently at 100 hits: measured 290 hits present, 100 delivered, no indication — the list looked complete. A half measurement must not look like a whole one.
persona_slug filters the statement hits to that persona's visibility
(sichtfeld) by joining through each statement's Sache. Event hits are
never filtered this way — events don't carry a bereich field, so
there's nothing to filter on. This is a documented limit, not an oversight:
a persona-scoped search here can under-represent events, and the contract
says so rather than implying full coverage. alle=true lifts the
statement-side filter and is logged as an event when it actually changes
what's visible.
limit sets the page size (default 100). Combined with persona=, it's
also capped at that persona's model-class ceiling (small 20 / medium 50 /
large 200) — an explicit value above the ceiling is rejected with 422
rather than silently truncated. The ceiling limits the page, not the
set: even a small persona can reach everything via offset.
A persona's own memory — GET /personas/{slug}/gedaechtnis
curl -sS "$COMPASS_URL/personas/atlas/gedaechtnis?q=deploy" -H "X-Compass-Key: $COMPASS_KEY_RO"
Full-text search (German tsvector) over one persona's memory entries —
newest first, only currently-held entries (gilt_bis null) unless
alle=true. Returns the persona's own entries plus visible company memory
(an entry with persona_slug=null is visible only if it carries at least
one tag inside the reading persona's sichtfeld, or the persona's
sichtfeld is empty). The MCP tool is
memory_search(persona_slug, q, art?, limit=20, lesende_persona?, alle=False)
— note the parameter is lesende_persona here, not persona_slug twice;
persona_slug already answers "whose memory," so the visibility filter
needs its own name.
Lessons — GET /lehren?q=
curl -sS "$COMPASS_URL/lehren?q=timeout" -H "X-Compass-Key: $COMPASS_KEY_RO"
Full-text search over lesson titel+was+lehre. Visibility here follows
a slightly different rule than memory: a lesson with no bereich at all is
considered general and stays visible to everyone — a lesson is written as a
general statement by form, a memory note is not. The MCP tool is
lehren_suche(q?, art?, status?, bereich?, lesende_persona?, limit?).
What isn't searched together
There is no single call that searches statements, memory, and lessons at
once — pick the surface that matches what you're looking for. If you're
not sure which table something lives in, GET /suche is the broadest
single call (statements + events across the whole tenant), followed by
checking the relevant persona's memory or the lesson log directly.