ECAI Private Knowledge Retrieval and Controlled LLM Access
Answer a question without opening the whole corpus
A team may want an assistant to explain an incident using internal notes, while keeping those notes out of a public search index and away from an unapproved model provider. That requires two decisions: who may read the records, and where their contents may be sent.
ECAI's private index and LLM bridge make those decisions explicit. Records are encrypted on disk, a reader is authorised for a particular corpus, and retrieval happens inside a trusted worker. The bridge sends selected excerpts only to a destination permitted for that corpus.
For example, a support runbook and a restricted operational record may need different readers and model destinations. Separate corpora let the operator express those differences. Within a corpus, permission applies to the entire collection; this implementation does not provide per-record access rules.
The sections below describe the repository implementation and the choices an operator needs to make before using private data. Start with synthetic records whose expected search results and permissions are easy to verify.
What becomes private
Each indexing batch becomes an immutable encrypted segment. The source records, term index and posting lists are encrypted together before writing the segment. The private path is separate from the public DETS document store, ingest journal, manifests, snapshots and shared hot cache. Private input is rejected by the public job path rather than silently entering public artifacts.
Search still needs a trusted execution environment. A worker decrypts each scanned segment, matches the existing canonical term keys and selects results. Every record in the scanned segment is decrypted in memory, including records that are not returned. This is encrypted storage with authorised plaintext processing; the implementation does not perform search over ciphertext.
The distinction matters for operational documents. A disk reader should not receive plaintext term lists, but a compromised host or privileged BEAM module remains inside the trust boundary. Segment counts, sizes, timestamps and access patterns are also observable. Deployment logging, crash dumps, swap and model service behaviour require their own controls.
Ownership, permissions and key handling
An operator configures private_corpora in the ecai application environment.
Each corpus names an owner, storage directory and scoped vault key references.
Readers and writers are separate lists: permission to append does not itself
grant permission to retrieve. The owner has both permissions. Access applies
to the whole corpus, so records requiring different access boundaries belong in
different corpora.
The HTTP handler obtains identity from
damage_auth:authenticated_account/1. A caller cannot select an owner or supply
key, storage-path or model options in a JSON body. Erlang entry points are
trusted application APIs; arbitrary code already running in the VM is not
isolated by these checks.
ecai_private_keys:provision/2 explicitly creates a scoped key entry through
the existing secrets service. Configuration holds references, not private key
material. Provisioning does not rotate an existing corpus in place. This v1
requires a new corpus/rebuild for rotation, record updates, deletion and
compaction. Revocation restricts subsequent access; it cannot retrieve plaintext
that has already been disclosed.
Interfaces in the current source
| Entry point | Purpose |
|---|---|
ecai_disk_indexer:index_private/4 |
Append one private batch |
ecai_private_index:search/4 |
Return ranked authorised source records |
ecai_private_index:fetch/3 |
Retrieve a record by its opaque reference |
ecai_llm_bridge:ask/4 |
Retrieve evidence and call a permitted destination |
ecai_ollama_rag:ask_private/4 |
Existing RAG entry point for the private path |
The four HTTP operations are POST requests:
| Route | Accepted fields |
|---|---|
/ecai/private/:corpus/index |
batch_id, records |
/ecai/private/:corpus/search |
query, optional limit |
/ecai/private/:corpus/fetch |
id |
/ecai/private/:corpus/ask |
question, destination |
These routes require the existing authenticated request path and protected transport. They do not introduce a new login scheme. Responses carry private, no-store cache directives. Request bodies do not accept arbitrary transport configuration or caller-selected callback modules.
Retrieval semantics and practical limits
The private implementation reuses ecai_terms/v1. Results are ordered by the
number of matched term keys, with an opaque record reference breaking ties.
That score measures lexical matches. It does not measure factual correctness,
semantic certainty or an experimentally demonstrated geometric advantage.
Input records must already be appropriately chunked.
The principal limits in the reviewed source are:
| Resource | Bound |
|---|---|
| Records per batch | 256 |
| Serialised batch input | 8 MiB |
| Individual record | 1 MiB |
| Encrypted segment | Approximately 32 MiB |
| Segments per corpus | 1,024 |
| Ciphertext scanned per query | 256 MiB |
| HTTP request body | 1 MiB |
| Search result limit | 50 |
The HTTP body cap can constrain a submission before the larger internal batch cap is reached. Immutable batch identifiers must be retained across retries. An existing identifier is refused; it is not proof that a retried payload is identical. A timeout can occur after a write commits.
The model boundary
The bridge checks the corpus's destination allowlist before decryption and checks it again before generation. It selects at most eight source records, clips each text excerpt to 6,144 bytes and caps the encoded prompt at 96 KiB. No matching sources means no model request. The bridge calls the existing client directly without a provider pool or automatic fallback.
A local destination requires the Ollama provider and a literal loopback
address. That establishes the immediate network destination, not what a local
service might subsequently forward. Remote use requires an explicitly
allowlisted destination and allow_remote_llm => true. The selected plaintext
then leaves the trusted retrieval process. TLS and store => false do not by
themselves establish the provider's complete retention policy.
The returned source references allow an application to revisit supporting records. Generated source labels are not proof that every sentence is supported. The bridge passes source material as untrusted evidence and provides no tool execution interface, but model answers still need application-level checking.
Try a small private-knowledge workflow
A useful first demonstration would load a small, permissioned collection of operational notes and economic-event records, then ask questions whose expected sources are known. An authorised account should retrieve the expected record; an unrelated account should receive no contents. A forbidden destination should be rejected before any model call. A question with no matching evidence should return the explicit no-source outcome.
Test revocation after an initially successful request, and check that the application does not display an old answer as the result of a refused request. Keep the retrieved source IDs with the expected results. This gives the team a repeatable check when permissions, records or model configuration change.
The repository's EUnit cases use test crypto and model providers. Real-backend behaviour, authentication and the deployed model route need an integration run of their own. The workflow here is a proposed evaluation.
Explore the implementation
- Index, search and fetch operations
- Corpus permissions and destination policy
- Encrypted segment storage
- Cryptography backend integration
- Authenticated HTTP interface
- Private retrieval and model dispatch
- Private-index test cases
The links pin the code used for this description. Consult the installation and backend documentation for the release you deploy; no new real-crypto or live-model result is asserted here.
