Architecting a Private, Multi-User Agentic RAG System
This is a blueprint for a self-hosted platform that gives every user their own agentic chatbot — one that answers only from documents the user is permitted to see, with no data leaving your infrastructure and no per-token API cost. Local models handle both embedding and reasoning; permissions are enforced at the data layer, not bolted on after retrieval.
Everything hangs off three operational flows served by six loosely coupled components.
Components
- Application server (Python) — the core: exposes the front-end API and talks to the message queue.
- Front-end (Streamlit or Angular) — auth, file management, and chat. Streamlit for a fast prototype; Angular for production.
- Blob storage (Minio) — S3-compatible object storage for the original uploaded files.
- Vector database (PostgreSQL + pgvector) — one database for both relational data (users, metadata, access groups) and embeddings. Because retrieval is a SQL join, you can filter vector search by permission in the same query — the key to multi-tenant isolation.
- LLM host (Ollama) — runs open-source models locally, typically two: a lightweight one for embeddings and a stronger one for chat and reasoning.
- Message queue (RabbitMQ) — decouples the front-end from heavy background work. Uploads return immediately while workers embed files asynchronously, which also lets you scale workers independently.
Flow 1: File submission
- The user authenticates and receives a token.
- They upload a file and assign it to one or more access groups.
- The API validates the token and stores the file in Minio.
- The file’s metadata — storage location and access groups — is written to Postgres.
- A message carrying the
file_idis published to RabbitMQ. - The API returns success immediately; embedding proceeds in the background.
Flow 2: Asynchronous embedding
- A worker consumes a message from the queue.
- Using the
file_id, it fetches the file’s metadata and location. - It downloads the file from Minio.
- It extracts the content, splits it into chunks, and embeds each chunk via the local Ollama embedding model.
- It writes the chunks and their vectors to Postgres, linked to the file’s access-control data.
Flow 3: Agentic chat and secure retrieval
- The user sends a prompt to their private agent.
- The system loads recent conversation history for context.
- A LangGraph workflow runs the agent’s reasoning.
- The LLM decides whether it needs more information — that is, whether to call a tool.
- If so, it runs
vector-search. The query is filtered at the database level to only the chunks this user may access. - Retrieved context feeds back into the LLM’s reasoning.
- The final, grounded answer streams back to the user.
What makes it work
- Permission-aware retrieval in one store. Putting metadata and vectors in the same Postgres/pgvector database means access control is a transaction-safe join, not a fragile post-filter. This is the security backbone.
- LangGraph for agent control. Modeling the agent as a state machine makes “when to call a tool” explicit and inspectable.
- Local agents are viable. With Ollama, a fully private, self-hosted agentic system runs on commodity hardware.
- Loose coupling scales. The message queue and separate services let you scale the slow part — embedding workers — without touching the rest.
Keep going
Privacy here isn’t a policy — it’s an architecture. Local models plus permission-scoped retrieval mean sensitive documents are answerable without ever being sent anywhere.

