Skip to content
LACE
  • v0.1 Current
  • Python
  • TypeScript Soon

Platform Guide

Enterprise Search

One search box over every source: documents, datasets, apps, agents, pipelines, KG facts, and messages. Results from three legs are fused and filtered so you only see what you can access, with a citation for what you can see.

Not a second ingest system. Enterprise Search is a query and fusion service over the durable Postgres catalog index (lace.search_catalog_entity), the document leg, and the KG leg. It does not re-crawl your sources.

How it works — three legs, one ranking

POST /v1/enterprise-search ──▶ EnterpriseSearchService.search()
                                   ├── catalog leg ── PostgresCatalogSearchBackend (tsvector + trgm) ──┐
                                   ├── document leg ── retrieve_segments (pgvector hybrid) ─────────────┤── RRF fuse (k=60) ── ACL finalize ── paginate
                                   └── kg leg ─────── chat_agent_service_runner / kg.facts (lexical) ──┘
                                                        │                     │
                                                     planner (lexical-only today, vector parked D3)
pythonsrc/lace/domain/enterprise_search/service.py
# service.py — three legs in parallel, RRF fuse, ACL finalize
results = await asyncio.gather(
  catalog_search(req),      # Postgres tsvector + pg_trgm over lace.search_catalog_entity
  document_search(req),     # pgvector hybrid (state.retrieve_segments)
  kg_search(req),            # kg.facts lexical (plainto_tsquery, AND semantics)
)
fused = reciprocal_rank_fuse(results, k=60)
visible = [r for r in fused if envelope_visible_to_principals(r, principals)]
LegSourceBackendWhat it catches
cataloglace.search_catalog_entityPostgresCatalogSearchBackend (default)Every LACE entity with browse and facets
documentpgvector segmentsstate.retrieve_segmentsParaphrase and semantic recall
kgKG entities + evidenceschema_extraction storeFacts with citations

Hybrid retrieval

A lexical leg and a vector leg run against the same corpus and their rankings are combined with reciprocal-rank fusion (RRF, src/lace/domain/enterprise_search/rrf.py, k=60). Lexical catches the exact identifier or clause reference that embeddings blur. Vector catches the paraphrase that keyword search misses. Fusing them avoids having to choose which failure mode to live with.

LegQuery transformIndexRecall
Lexicalplainto_tsquery / to_tsvector + pg_trgmtsvector GIN + trigram GIN on lace.search_catalog_entityExact identifiers, BM25-like ranking
VectorEmbedding of query (same encoder as ingest)pgvector IVFFLAT / HNSW over segment embeddingsSemantic recall

The vector leg is currently parked and the planner is lexical-only (src/lace/domain/enterprise_search/planner.py, vector re-enable is D3). The fusion point and RRF scorer already exist, so the vector leg can reattach without changing callers.

Permissions are not post-filtering

Retrieval resolves your principals from auth against each source's permission snapshots taken at sync time (datasets). The platform enforces ACL in SQL as a prefilter, so unauthorized rows never leave Postgres, and again in the Python finalize. Facet counts are recomputed over the visible set only so they never leak a restricted document's existence. Code: src/lace/domain/enterprise_search/acl.py + catalog.py.

GPU rerank

After fusion, the sidecar at services/reranker (GPU, cross-encoder) may rerank results. The reranker sees the original query plus each candidate's text and re-scores with a cross-attention model. Opt in per request (rerank: true) or per tenant default. When the reranker is saturated or disabled, the RRF ranking is the final ranking.

Stable identity

Every retrieved result has a stable triple — document_id, block_id, span [start, end] — that survives re-ingest. That triple is what citations point to and what the knowledge graph's evidence checks gate on. See src/lace/domain/document_evidence and src/lace/domain/ingest.

Query shape

terminalbash
curl -X POST https://api.laceplatform.com/v1/enterprise-search \
  -H "Authorization: Bearer $LACE_API_KEY" \
  -d '{
    "query": "Q3 warranty reserves",
    "limit": 10,
    "facets": ["entity_type","dataset"],
    "rerank": true
  }'
FieldWhat it does
queryLexical query (today, plainto_tsquery; vector leg parked, query planner is lexical-only)
limit / cursorPaginated; stable over index updates
facetsRequest facet counts (entity type, dataset, connector, etc.)
rerankWhen true, results are reranked on GPU via services/reranker
and_matchBoosts hits that matched all query terms (lexical AND semantics) over OR-recall hits

The durable catalog index

lace.search_catalog_entity is a Postgres table (tsvector + pg_trgm + GIN indexes) fed by CatalogIndexer: project → diff → sync (bootstrap + reconcile) plus event-driven DocumentStubSyncWorker on ingest success and dataset CRUD. The write store lives at src/lace/api/stores/repositories/search_catalog.py::PostgresCatalogStore. Versioning is single monotonic space (versioning.py) so external_gte gating is coherent and bootstrap is idempotent (second run = drift 0). No per-query live projection on the hot path. The durable index is O(index). The live-projection fallback surfaces a catalog_index_fallback warning when it fires.

Citations

Every document and KG result carries its source span — document, block, and char offsets — so the answer can point to the paragraph that supports it. The RAG path uses the same legs; enterprise search is the browse and answer surface, RAG is the grounded generation surface.

RAG — grounded generation

RAG is the same retrieval stack plus a generation pass that is required to cite. The prompt is assembled from the top-N fused, ACL-filtered, reranked spans. The model is instructed to only assert what a span supports. The response carries the spans back as citations. The retrieval and generation spans share trace ids (see observability) so you can click a citation from the answer back to the document block.

pythonprogrammatic RAG
from lace.domain.rag import rag_answer

answer = await rag_answer(
    query="What did Q3 say about warranty reserves?",
    dataset_ids=["0141b..."],
    tenant_id="acme",
    principals=["user:4821"],
)
# → { text, citations: [{ document_id, block_id, span: [start, end], score }] }

Ops

  • python -m lace.domain.enterprise_search.ops bootstrap — backfill the catalog.
  • python -m lace.domain.enterprise_search.ops reconcile — drift detection and sync.
  • See src/lace/domain/enterprise_search/README.md and docs/agent/enterprise-search.md for the full design.

Next: datasets or the marketing page.