How to Integrate LlamaIndex for lending with Supabase for multi-agent systems
Lending workflows need two things that usually live in different systems: retrieval over policy and loan documents, and a durable shared state layer for agents. LlamaIndex gives you the retrieval and reasoning layer for lending knowledge, while Supabase gives your multi-agent system Postgres-backed memory, auth, and event storage.
Used together, you can build lending agents that read borrower files, check policy rules, store decisions, hand off tasks between agents, and keep an auditable trail in one place.
Prerequisites
- •Python 3.10+
- •A Supabase project with:
- •
SUPABASE_URL - •
SUPABASE_SERVICE_ROLE_KEY
- •
- •A Postgres database in Supabase with a table for agent state
- •LlamaIndex installed with the lending-related packages you need
- •Access to your lending documents:
- •PDFs
- •policy docs
- •underwriting guidelines
- •loan application metadata
- •An embedding or LLM provider configured for LlamaIndex
Install the core packages:
pip install llama-index supabase python-dotenv psycopg2-binary
If you are using a vector store or LlamaIndex integrations for document ingestion, install those too.
Integration Steps
1) Initialize Supabase and load config
Start by wiring environment variables into both systems. In production, keep the service role key server-side only.
import os
from dotenv import load_dotenv
from supabase import create_client, Client
load_dotenv()
SUPABASE_URL = os.environ["SUPABASE_URL"]
SUPABASE_SERVICE_ROLE_KEY = os.environ["SUPABASE_SERVICE_ROLE_KEY"]
supabase: Client = create_client(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY)
Create a table for shared agent state:
create table if not exists agent_state (
id uuid primary key default gen_random_uuid(),
loan_id text not null,
agent_name text not null,
state jsonb not null default '{}'::jsonb,
created_at timestamptz default now(),
updated_at timestamptz default now()
);
This table becomes the coordination layer for your underwriting, fraud, and compliance agents.
2) Load lending documents into LlamaIndex
Use LlamaIndex to ingest policy docs or borrower files. The point here is not just search; it is giving each agent grounded access to lending context.
from llama_index.core import SimpleDirectoryReader, VectorStoreIndex
documents = SimpleDirectoryReader("./lending_docs").load_data()
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine(similarity_top_k=3)
response = query_engine.query("What are the minimum income requirements for unsecured personal loans?")
print(response)
For lending systems, keep document sets separated by domain:
- •underwriting policy
- •KYC/AML rules
- •product terms
- •exceptions approval matrix
That keeps retrieval tight and reduces cross-contamination between agents.
3) Store and retrieve agent state in Supabase
Multi-agent systems need shared memory. Use Supabase as the source of truth for loan workflow state so agents do not rely on in-process variables.
def save_agent_state(loan_id: str, agent_name: str, state: dict):
data = {
"loan_id": loan_id,
"agent_name": agent_name,
"state": state,
}
return supabase.table("agent_state").insert(data).execute()
def get_latest_agent_state(loan_id: str):
result = (
supabase.table("agent_state")
.select("*")
.eq("loan_id", loan_id)
.order("updated_at", desc=True)
.limit(1)
.execute()
)
return result.data[0] if result.data else None
save_agent_state(
loan_id="LN-10021",
agent_name="underwriting_agent",
state={"status": "reviewing", "risk_flags": ["thin_credit_file"]}
)
This pattern lets one agent write findings and another pick them up later without passing large payloads around your orchestration layer.
4) Combine retrieval + persistence in one underwriting flow
Now connect both tools. One agent retrieves policy guidance from LlamaIndex, then writes a structured decision artifact to Supabase.
def underwrite_loan(loan_id: str, applicant_profile: dict):
prompt = f"""
Review this applicant against lending policy.
Applicant:
{applicant_profile}
Return:
- decision
- reasons
- missing_documents
"""
policy_answer = query_engine.query(prompt)
decision_record = {
"decision_text": str(policy_answer),
"applicant_profile": applicant_profile,
"status": "completed"
}
supabase.table("agent_state").insert({
"loan_id": loan_id,
"agent_name": "underwriting_agent",
"state": decision_record,
}).execute()
return policy_answer
result = underwrite_loan(
loan_id="LN-10021",
applicant_profile={
"name": "Amina Patel",
"income": 72000,
"debt_to_income": 0.31,
"credit_score": 689
}
)
print(result)
In a real multi-agent setup:
- •underwriting agent queries policy via LlamaIndex
- •compliance agent checks exceptions
- •document agent tracks missing files
- •coordinator reads/writes status in Supabase
5) Add a handoff pattern between agents
Use Supabase rows as task messages. Each agent claims work, processes it, then writes back results.
def create_task(loan_id: str, task_type: str, payload: dict):
return supabase.table("agent_state").insert({
"loan_id": loan_id,
"agent_name": task_type,
"state": {"status": "queued", **payload},
}).execute()
def process_compliance_task(loan_id: str):
task = get_latest_agent_state(loan_id)
if not task or task["agent_name"] != "compliance_agent":
return None
check = query_engine.query(
f"Check this loan against compliance rules:\n{task['state']}"
)
supabase.table("agent_state").insert({
"loan_id": loan_id,
"agent_name": "compliance_agent",
"state": {"status": "completed", "result": str(check)},
}).execute()
create_task(
loan_id="LN-10021",
task_type="compliance_agent",
payload={"rule_set": ["KYC", "AML"], "priority": "high"}
)
That gives you durable orchestration without introducing a separate message broker on day one.
Testing the Integration
Run a simple end-to-end test that queries lending guidance and persists the result.
test_loan_id = "TEST-LN-001"
answer = query_engine.query(
"What documents are required for income verification on a mortgage application?"
)
supabase.table("agent_state").insert({
"loan_id": test_loan_id,
"agent_name": "test_agent",
"state": {"question": "income verification docs", "answer": str(answer)}
}).execute()
saved = get_latest_agent_state(test_loan_id)
print(saved["state"]["question"])
print(saved["state"]["answer"][:120])
Expected output:
income verification docs
Income verification typically requires recent pay stubs...
If that works, your retrieval path is working and your persistence path is working. That is the minimum bar before plugging this into an orchestrator like LangGraph or a custom queue worker.
Real-World Use Cases
- •
Loan origination assistant
- •One agent gathers borrower data.
- •Another checks policy from indexed lending docs.
- •Supabase stores each step for audit and retry.
- •
Exception review workflow
- •Underwriting agent flags edge cases.
- •Compliance agent reviews exceptions against stored rules.
- •Decision history stays queryable in Postgres.
- •
Document chase automation
- •A document agent identifies missing files.
- •A follow-up agent generates borrower requests.
- •Supabase tracks status across all agents and all loans.
Keep learning
- •The complete AI Agents Roadmap — my full 8-step breakdown
- •Free: The AI Agent Starter Kit — PDF checklist + starter code
- •Work with me — I build AI for banks and insurance companies
By Cyprian Aarons, AI Consultant at Topiax.
Want the complete 8-step roadmap?
Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.
Get the Starter Kit