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