Read-Only LLM Personal Finance Assistant: Architecture, Access Control, and Implementation

    This article shows how to build a personal finance assistant powered by large language models (LLMs) under a read-only constraint. It covers the assistant's capability boundaries, a layered architecture, and how to enforce read-only access through dedicated credentials, an allowlist gateway, and audit logging. It also covers natural-language intent parsing, spending categorization, anomaly detection, APR-based interest calculations, visualization, hallucination control, and privacy and compliance practices. It is intended for developers who want an assistant that explains and analyzes financial data without ever moving money.

    As large language models mature, bringing natural-language interaction into personal finance management has become a valuable technical path. The core idea is straightforward: with the user’s authorization, they link their accounts, ask questions in conversation to check balances and review transactions, and let the system categorize money flows and detect anomalies.

    Because the system handles sensitive financial data, its design must take one principle as its foundation: read-only, no privilege escalation, and no actions performed on the user’s behalf. This article walks through the full technical design of this scenario, from architecture to implementation.

    Core Requirements and Capability Boundaries

    A responsible personal finance assistant should have its capability boundaries clearly defined at the design stage:

    • Read only, never write. The system has no write capability at all. It cannot move money, initiate payments, or modify accounts, and it must never execute transactions or investment actions on the user’s behalf.
    • Explain and analyze only, no recommendations. The system helps users understand their existing data. It does not output financial advice that steers decisions, and it does not embed ads or sponsored content.
    • Make uncertainty explicit. Every statistical conclusion should be traceable to the underlying transaction details. For material amounts, the user should be prompted to verify them against their bank’s records.

    These three boundaries are more than product positioning. They are technical constraints on security and compliance, and they must be enforced separately in the data access layer and the model output layer.

    Layered Architecture

    A layered architecture keeps each responsibility focused and each boundary well defined:

    1. ┌────────────────────────────────────────────────┐
    2. │ Interaction layer: chat UI / charts / dashboards│
    3. ├────────────────────────────────────────────────┤
    4. │ Orchestration: intent -> tasks -> response │
    5. ├────────────────────────────────────────────────┤
    6. │ Analysis: classification, anomalies, metrics │
    7. ├────────────────────────────────────────────────┤
    8. │ Data access: read-only credentials, masking │
    9. ├────────────────────────────────────────────────┤
    10. │ Infrastructure: object storage, MQ, logging │
    11. └────────────────────────────────────────────────┘

    The data access layer is the security chokepoint. No matter how the upper layers orchestrate requests, every read of account data must go through a single read-only gateway, making write operations impossible at the physical level. The general-purpose capabilities offered by cloud platforms, such as object storage, message queues, and logging services, can handle data persistence, asynchronous processing, and audit trails.

    Read-Only Access and Security Boundaries

    Read-only access cannot live only in documentation. It must be enforced in engineering. Common ways to do that:

    1. Dedicated read-only credentials. Provision a separate access credential for the data integration that is scoped to queries only. Its permission set excludes every transfer, debit, and amendment endpoint, limiting the system’s capabilities at the root.
    2. A single access gateway. All data requests are forwarded through one gateway. The gateway has a built-in allowlist that permits only query-type methods, and it masks sensitive fields in the responses it returns.
    3. End-to-end audit logging. For every data access, record the caller, the timestamp, and the field scope, and write it to the logging service. This makes it possible to trace activity after the fact and to raise alerts on anomalies.

    Example gateway validation logic:

    1. READ_ONLY_ALLOWLIST = {"get_balance", "list_transactions", "get_bill_summary"}
    2. def access(account_id: str, action: str, payload: dict):
    3. if action not in READ_ONLY_ALLOWLIST:
    4. raise PermissionError(f"Action not allowed in read-only mode: {action}")
    5. # Enforce masking: hide full card numbers, CVV, and other sensitive fields
    6. result = account_service.dispatch(account_id, action, payload)
    7. return desensitize(result)

    With three layers of defense—read-only credentials, a gateway allowlist, and masked output—even a logic flaw in the orchestration layer cannot cross the boundary to perform a write.

    Parsing Natural-Language Intent

    Users tend to ask questions in everyday language, for example “Where did my money go this month?” or “Why was there an extra fee?” The system must first recognize the intent and then map it to concrete data analysis tasks. The typical processing flow:

    • Intent classification: Determine whether the question is about a balance lookup, spending analysis, anomaly investigation, metric calculation, or chart generation.
    • Entity extraction: Identify the time range (this month, the last 30 days), account objects, amounts, category keywords, and so on.
    • Task orchestration: Break a complex question into a set of executable read-only queries and analysis steps.

    One point deserves emphasis: the model is responsible for understanding the question and organizing the answer, while the actual data computations should be handled by deterministic analysis code. This keeps the model from “doing the math” from memory. That division of labor is the key to controlling accuracy.

    Implementing the Analysis Capabilities

    1. Spending categorization

    Transactions are grouped into preset categories (dining, transport, housing, subscriptions, entertainment, and so on). This can be done with rule matching combined with a lightweight classification model:

    1. def categorize(merchant: str, memo: str) -> str:
    2. rules = {
    3. "Dining": ["restaurant", "coffee", "takeout"],
    4. "Transport": ["subway", "gas", "rideshare"],
    5. "Subscriptions": ["membership", "monthly fee", "renewal"],
    6. }
    7. text = (merchant + memo).lower()
    8. for category, keywords in rules.items():
    9. if any(k in text for k in keywords):
    10. return category
    11. return "Other"

    After the statistics are computed, the system can report each category’s share and its change versus the previous period. That helps users quickly see where their money is going.

    2. Anomaly detection

    Some issues are easy to overlook: duplicate charges, unexplained fees, and quietly rising bills. Several deterministic detection rules can address them:

    • Duplicate charges: Within the same time window, multiple transactions at the same merchant with highly consistent amounts and frequency.
    • New fees: A transaction marked as a “fee” that has no corresponding service description.
    • Subscription price increases: The per-period charge from the same subscription merchant exceeds its historical average by more than a set threshold.
    1. def detect_anomalies(transactions):
    2. alerts = []
    3. # Flag subscription charges that quietly creep up
    4. for merchant, records in group_by_merchant(transactions).items():
    5. amounts = [t.amount for t in records if t.type == "subscription"]
    6. if len(amounts) >= 3 and amounts[-1] > 1.2 * mean(amounts[:-1]):
    7. alerts.append({"type": "subscription price increase", "merchant": merchant})
    8. return alerts

    This kind of analysis is valuable because it surfaces the “slow leaks” in a user’s spending that would otherwise go unnoticed.

    3. Macro-level metrics

    Questions like “Am I spending more than I earn?” or “What does the annual interest rate mean, and how much interest did it generate this year?” should be answered by first explaining the terminology and then plugging in the real data. Take interest under an annual percentage rate (APR) as an example. A daily-interest model can be used:

    1. def daily_interest(principal: float, apr: float, days: int) -> float:
    2. daily_rate = apr / 100 / 365
    3. return principal * daily_rate * days

    First explain to the user that “the APR is the rate expressed on an annual basis; to get a concrete amount, apply it to the actual principal and the actual number of days the interest accrues.” Then show the computed results. This significantly improves how understandable and credible the answer is.

    Visualization and Custom Dashboards

    Readable analysis results depend on visualization. The system can generate weekly spending trend charts, a bill-due calendar, donut charts of category shares, and user-customizable dashboards. On the implementation side, the orchestration layer assembles the data the intent requires, and the front end renders it with general-purpose chart components. Users can also adjust parameters such as the time range and category dimensions at any time to create an interactive analytical view.

    Accuracy and Hallucination Control

    Large language models risk producing answers that seem plausible but are wrong. That risk needs special attention in financial scenarios:

    • Push computation down. All amounts, interest rates, and totals are computed by deterministic code. The model never produces numbers on its own.
    • Traceable results. Any conclusion can be expanded in one click to show the transactions behind it, so users can verify it.
    • Explicit disclaimers. For material amounts, the system proactively tells users to treat their bank statements as the source of truth, avoiding any false sense of certainty.

    Privacy and Compliance Best Practices

    Personal financial data is highly sensitive. Engineering practice should follow the principles of least privilege, minimal collection, encrypted storage, and access auditing. Data should be encrypted both in transit and at rest, sensitive fields should be masked by default, and every access should be logged. Users should be able to unlink their accounts and delete their data at any time. Features should also clearly explain how user data is used, keeping everything transparent and auditable.

    Summary

    Technically, building a read-only personal finance assistant is not mysterious. What matters is carrying the “read-only” safety constraint through every layer of credentials, gateways, and auditing. It also means drawing a clear line between “understanding” and “computation,” using deterministic code to keep the numbers accurate, and adding visualization and traceability to make the tool usable. Following these principles, developers can deliver an intelligent analysis tool that genuinely helps users understand their finances while preserving security and compliance.