Embedding Limma in your platform¶
This page is for an engineering team whose platform runs its own agents and wants Limma as the layer that holds a customer's records and documents and answers over them. Your platform keeps what it already does — connecting to the customer's sources, running the agents, the user interface. Limma takes what your connectors fetch, structures it into facts at addresses, and answers your agents' questions over MCP with every match, the sentence behind each, and a statement of whether the list is complete.
Each section names the page that holds the detail.
The shape¶
customer sources ──> your connectors ──> Limma (inside the customer's environment)
(files, exports, (fetch, track ├── /v1/workspaces/{id}/documents files
databases) changes) ├── /v1/public/ingest text
├── /v1/public/upload_export export files
├── /v1/public/connect records, tables
└── /mcp <── your agents, one key per caller
- One process, one port. The REST API and the MCP endpoint are the same server.
- Inside the customer's environment. Limma ships as a compiled image that starts only under a license Limma issues; nothing of a document leaves the environment (Install, Licensing).
- Your model, inside the environment. Limma reads text that needs judgment with an OpenAI-compatible endpoint you point it at (Choose the model).
1. Install¶
Follow Install Limma inside your network with the image and the license file Limma sends you. The license file goes where Licensing says; without it the image does not start. The image reports usage counts — never content — every six hours, or writes a monthly file where outbound traffic is blocked (What is sent).
2. Keys¶
Every caller has its own key, issued as key:organisation:role[:workspaces[:documents]]
(Keys and roles). A typical embedding uses:
| Key | Role | Used by |
|---|---|---|
| a loading key | service |
your connectors, over REST: put documents and records in |
| an agent key | analyst |
your agents: every question, including one whose condition must be read from documents with your model — that read spends, within the search's own budget |
| a read-only key | viewer |
an assistant that must never spend or change anything: it is answered from what is already structured or read, and a search that would need a new read is refused |
| an admin key | admin |
your operators: keys, deletion, the audit trail |
An analyst key may also load data. To keep your agents to questions, expose only the
question tools over MCP — the operator's LIMMA_MCP_TOOLS=limma_search,limma_facts,limma_region
— and give the agent runtime the MCP endpoint, not the REST API. Every call, by every key,
is in the audit trail under that key's id.
3. Put data in¶
Pick the door by the shape of the data, not by its source.
| The data | Door | What Limma does with it |
|---|---|---|
| A folder of files, an S3 / Azure Blob / Google Cloud Storage bucket, a Google Drive folder, a SharePoint or OneDrive for Business library | limma sync <source> (pip install limma-ai) — one command, run again to keep it current (Connectors) |
sends each file through the upload door below — under its path, or under its Drive or SharePoint id — sends a changed one as a new version, and deletes what is gone with --delete |
| Files: PDF, Word, PowerPoint, Excel, HTML, email, RTF, XML, text; images (PNG, JPG, TIFF) and scanned pages, read by OCR | POST /v1/workspaces/{id}/documents (multipart; SDK upload_document) |
reads the text and tables, keeps every fact's exact position |
| Text you already extracted | POST /v1/public/ingest (limma_ingest) |
the same, from text |
| Records: JSON, JSONL, CSV, TSV, Excel exports | POST /v1/public/connect (limma_connect) |
drafts the schema from the records, asks only where it cannot tell, loads each record as facts |
| An export file your connector holds and the API host does not | POST /v1/public/upload_export (multipart; SDK upload_export), then connect with its export_id |
the same as a file on the host: stored for this workspace, read by the same reader, deleted by the pull that loads it |
| A database table | POST /v1/public/connect with database_url + table |
reads it, read-only; every fact can re-read its row |
A page is read as it is laid out, and a number at the scale its own page or sheet states. A PDF page set in two columns of text is read column by column, never straight across; a table's columns stay apart even when no rules are drawn between them. An Excel sheet is read at the scale it states itself ("in thousands"): a sheet that states none is read as written, never at a scale another sheet of the workbook states.
An export file is named by where it is. On the API host — the ./imports directory
the install mounts — connect names its path. Anywhere else — your connector's own
machine, a deployment whose API processes share no disk — upload it first and connect the
export_id the upload answers: A file the API host cannot reach.
The upload is REST and SDK only; your agents connect the id over MCP.
File each document under what it is about. Send entity_name with a document that is
about one thing — a person's CV, a contract, a company's filing. Limma then reads that
document when a question asks about that thing. A document sent without one is still
searchable by its facts, but cannot be enumerated as "every document about X".
Load the register, then the documents. Questions about sets — "which contracts…",
"which clients…", "who…" — are answered over the things a workspace holds as records. Load
the register (the contract list, the client list, the directory) through connect, then
the documents filed under its entries. Records pages: A document-store export,
A CSV or Excel export.
A sync tool's output (Airbyte or one like it) is read through the same doors — key each record by the source's own id, never the tool's: Loading a sync tool's output.
Keep it in step with the source. Your connectors send changes, not snapshots: the external id, versions, deletion and the per-user access list are on Sync and access. For a folder or a bucket there is nothing to build: Connectors is one command that does it through those same doors.
4. Connect your agents¶
Point your MCP client at https://<limma-host>/mcp with the agent's key
(MCP over HTTP). The default tools are:
| Tool | What it answers |
|---|---|
limma_search |
the closed set of matches for a question, each with why, the near misses, and whether the list is complete |
limma_facts |
every fact at an address, with receipts |
limma_region |
the exact words a receipt points at |
limma_ingest, limma_connect |
loading, for keys allowed to load |
A search answer carries, for each match, the conditions it met, the value, and the
sentence from the source; the matches one condition short; counts of everyone else; and
complete, or incomplete_because with the reasons (Search,
A count). Tell your agent to answer from those fields and to quote the
sentence when it names a match.
5. What it does not do yet¶
Say these to your customers as they are:
- Access per record of a records export: an access list applies to a whole export source, not to one row (Sync and access).
- Questions about ideas rather than things — "where do we discuss pricing risk?" — return passages, without a completeness statement.
- Conditions judged from free text (e.g. "sold surgical staplers through public tenders") are read by your model for each candidate: exact where the data is structured, good but not perfect where text must be judged — and a first read of a new condition takes time. A candidate whose text does not prove a condition is returned as one condition short, with the sentence, never as a match.