How to Integrate LlamaIndex for lending with Supabase for multi-agent systems

By Cyprian AaronsUpdated 2026-04-22
llamaindex-for-lendingsupabasemulti-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

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

Related Guides