Agent Playbook, build tutorial

How to Build an AI Agent for Reconciliation

A walkthrough of how to build a reconciliation agent that matches money across the places it moves: your payment gateway settlements, your bank statement, your marketplace payouts, and your own sales ledger. It normalizes every source into one shape, matches on reference and amount, scores the fuzzy cases, checks each settlement against the fee it should have cost, and routes only the genuine exceptions to a person. Every code step is shown in three frameworks, Google ADK, the Claude SDK, and LangGraph, so you can build it on the stack your team already knows. The code here is the agent's core logic; standing it up in production also means adding the setup, integrations, and hardening around it.

Why manual reconciliation leaks money

Reconciliation is the unglamorous work of proving that the money you think you earned is the money that actually arrived. For an Indian SME it is rarely one comparison. A single sale can pass through a payment gateway that deducts a fee and settles a day or two later, a marketplace that nets commission and returns, and a bank account that shows only the final credit, while your books record the gross. Matching all of that by hand, every month, is slow, and the gaps are where money quietly leaks: a gateway fee charged at the wrong rate, a settlement that never landed, a short payment booked as paid, a refund counted twice.

A spreadsheet does not scale to thousands of rows across four sources. An agent does. It pulls each source, normalizes them to one shape, matches the obvious cases in deterministic code, checks every payout against the net it should have been, and surfaces only the handful that need a human, with the reason already attached. If you want to see the idea on your own export before you build, the free data analyzer already flags where the numbers do not add up.

Pick a framework: ADK, Claude SDK, or LangGraph

You do not build the agent loop from scratch. A framework gives you the moving parts, and you supply the logic: a step is a model plus an instruction plus a few typed tools. The code tabs on the build steps show the same match, classify, and post flow three ways, so you can read it in the stack you already use.

Google ADK composes the steps with a SequentialAgent and a Runner, the Claude SDK drives a tool-use loop, and LangGraph builds an explicit graph. The normalization, the exact and fuzzy matching, and the settlement check are deterministic and identical in all three; only the orchestration and the model calls differ.

The architecture

Every source flows through one loop: ingested and normalized, matched exactly then fuzzily, checked for fees, then either posted when confident or sent to a person when not. Exceptions are classified and a fix is proposed; guardrails for idempotency, tolerance, and audit wrap the whole thing.

1. Ingest and normalize gateway, bank, ledger to one shape 2 to 3. Match exact, then scored fuzzy 4. Check settlements expected net vs payout 5. Confident match? within tolerance and gate? 7. Post and reconcile mark matched, write audit 5 to 6. Classify label + propose entry human approves 8. Run and monitor match rate, leakage caught Guardrails idempotency tolerance audit log yes, post exception approved feedback loop

Before you build: prerequisites

Inputs and access

Your sales ledger or ERP, a bank feed or statement source, gateway settlement reports, and any marketplace payout reports you sell through.

Systems to connect

A model API key (Gemini, Claude, or both), your gateway and marketplace APIs or report exports, and your accounting system (Tally, Zoho Books) for posting.

Governance

Your amount tolerance and auto-post ceiling, the chart of accounts for corrections, DPDP Act 2023 handling, and who approves exceptions and write-offs.

Setup: install and keys

Install the packages and set your Gemini key. That is enough to run the runnable file below, which reconciles sample sources and classifies an exception locally. For production you also connect, with provider-specific credentials, your gateway and marketplace report sources, your bank feed, and your accounting system for posting approved entries.

Shell (install and key)
# Python 3.10 or newer
python -m venv venv && source venv/bin/activate

# The runnable path below uses Google ADK + Gemini:
pip install google-adk google-genai

# Your Gemini key from aistudio.google.com/apikey:
export GEMINI_API_KEY="your-key-here"

The stack, and why each piece

Layer Pick Why
Ingestion Gateway, bank, and marketplace sources (Razorpay, PayU, Cashfree, your bank, Amazon, Flipkart) Pull settlements, bank credits, and order data. Where there is no clean API, parse the settlement report or bank statement (CSV, Excel, MT940) instead of re-keying it.
Normalization A common transaction schema in Python Every source names its columns differently. Map them all to one shape (date, amount, reference, counterparty, source) so matching is uniform and testable.
Matching engine Deterministic Python: exact, then scored fuzzy Match on reference and amount first, then a scored pass on amount, date window, and counterparty. This is code you can unit-test, not a model guess.
Model Gemini 2.5 Flash, Claude, or any model behind LangChain Reads messy bank narrations, classifies each leftover exception (fee, short pay, timing, chargeback), and drafts the correcting entry. The code tabs show the same call three ways.
Agent framework Google ADK, the Claude SDK, or LangGraph Drives ingest, match, check, classify, and post, with a human-review branch. Pick the one your team runs; the logic is identical.
Data and audit Postgres or your ERP API, plus an immutable log Holds the ledger, the matched and unmatched sets, the tolerance rules, and an audit trail of every match, every posting, and every override.

Build it, step by step

The model and orchestration steps show the same logic in Google ADK, the Claude SDK, and LangGraph. Use the tabs to switch. The normalization, matching, and settlement steps are deterministic Python and identical everywhere. These are the building blocks, and they call your own systems (the ledger, the bank feed, the gateway report) by name; the complete file you can run today is in "Put it together" below.

1

Ingest and normalize every source

Reconciliation fails before it starts when each source is a different shape. So the first job is plumbing, not intelligence: pull the gateway settlement, the bank statement, and your sales ledger, then map each one to a single common record. A payout report calls it 'settlement_amount', the bank calls it 'credit', your ledger calls it 'amount'. Once they all become the same Txn shape, every later step treats them identically. This is plain Python, the same in every framework.

Python (normalize)
from dataclasses import dataclass
from datetime import date

@dataclass
class Txn:
    source: str          # "gateway" | "bank" | "ledger"
    ref: str             # order id, UTR, or settlement id
    amount: float        # signed: credit positive, debit negative
    on: date
    party: str = ""

def normalize(rows: list[dict], source: str, cols: dict) -> list[Txn]:
    # cols maps THIS source's column names onto the common shape
    return [Txn(source, str(r[cols["ref"]]), float(r[cols["amount"]]),
                parse_date(r[cols["date"]]), str(r.get(cols.get("party", ""), "")))
            for r in rows]
# Now gateway, bank, and ledger are all list[Txn], ready to match the same way.
2

Exact match on reference and amount

Most lines reconcile themselves, so clear those first and cheaply. The agent indexes the ledger by reference, then for each bank or gateway line looks up the same reference and confirms the amount agrees within a tolerance (a rupee, to absorb rounding). A hit is a confident, auditable match and needs no model and no human. Everything that does not match exactly falls through to the next pass. Deterministic, and identical in every framework.

Python (exact match)
def exact_match(ledger: list[Txn], feed: list[Txn], tol: float = 1.0):
    by_ref = {t.ref: t for t in ledger if t.ref}
    matched, leftovers = [], []
    for t in feed:
        m = by_ref.get(t.ref)
        if m and abs(m.amount - t.amount) <= tol:
            matched.append((m, t))          # same reference, same amount
        else:
            leftovers.append(t)             # send to the fuzzy pass
    return matched, leftovers
# Reference plus amount within tolerance is a match you can defend in an audit.
3

Score the fuzzy cases, do not guess them

The leftovers are where judgement usually creeps in, so replace judgement with a score. For each unmatched line the agent looks for an open ledger entry whose amount is within tolerance and whose date is within a window, and rewards a matching counterparty. That produces a confidence between 0 and 1. Only matches above a gate are accepted; weaker ones are never auto-closed, they go to a human with the suggestion attached. The score is what keeps a plausible-but-wrong match out of your books.

Python (scored fuzzy)
def fuzzy_match(open_ledger: list[Txn], leftovers: list[Txn],
                days: int = 3, tol: float = 1.0, gate: float = 0.6):
    pairs, review = [], []
    for t in leftovers:
        best, score = None, 0.0
        for m in open_ledger:
            if abs(m.amount - t.amount) > tol:        continue
            if abs((m.on - t.on).days) > days:        continue
            s = 1 - abs((m.on - t.on).days) / (days + 1)   # closer date scores higher
            if same_party(m.party, t.party): s += 0.3
            if s > score: best, score = m, round(s, 2)
        (pairs if best and score >= gate else review).append((t, best, score))
    return pairs, review            # review = low confidence, a human decides
4

Check each settlement against the fee it should have cost

A gateway payment almost never matches its order on amount, because the gateway keeps a fee and adds GST on that fee before settling. The fee depends on the rail, a percentage on cards and a small flat fee on UPI, which carries no percentage charge, so the rate is a parameter you set per method. That is not a mismatch to chase, it is arithmetic to verify. The agent recomputes the net it expects, gross minus the fee minus GST on the fee, and compares it to what actually landed. A zero gap means the difference was just the fee and the order is fine; a negative gap is a short settlement or a wrong fee rate worth flagging. This is deterministic and is where fee leakage gets caught.

Python (settlement check)
def check_settlement(gross: float, net: float,
                     fee_rate: float = 0.02, gst: float = 0.18, tol: float = 1.0):
    fee = round(gross * fee_rate, 2)
    expected_net = round(gross - fee - fee * gst, 2)   # GST applies on the fee
    gap = round(net - expected_net, 2)
    if abs(gap) <= tol:
        return "ok", gap                  # the difference was only the fee
    return ("short_settlement" if gap < 0 else "over_credit"), gap
# Run on every payout: this is how a fee charged at the wrong rate gets noticed.
5

Classify each exception with the model

What is left is the genuinely ambiguous: a credit with a cryptic narration, a gap that is not just a fee, a debit you did not expect. Here the model earns its place, reading the narration and the numbers and sorting each exception into a fixed set of labels so the right workflow can run. It is classification into a closed list, not free text, so the output is predictable. The tabs show the same classification in Gemini, Claude, and LangChain; the labels never change.

from google import genai
client = genai.Client()
LABELS = ["bank_charge", "short_payment", "timing_difference",
          "missing_settlement", "duplicate", "chargeback", "other"]

def classify(txn: Txn, gap: float) -> str:
    return client.models.generate_content(
        model="gemini-2.5-flash",
        contents=[f"Classify this reconciliation exception into one of {LABELS}. "
                  f"Reply with the label only. "
                  f"Narration: {txn.party} {txn.ref}. Gap: Rs {gap}."]).text.strip()
from anthropic import Anthropic
client = Anthropic()

def classify(txn: Txn, gap: float) -> str:
    res = client.messages.create(model="claude-opus-4-8", max_tokens=64,
        tools=[{"name": "set_label", "input_schema": {"type": "object",
            "properties": {"label": {"type": "string", "enum": [
                "bank_charge", "short_payment", "timing_difference",
                "missing_settlement", "duplicate", "chargeback", "other"]}},
            "required": ["label"]}}],
        tool_choice={"type": "tool", "name": "set_label"},
        messages=[{"role": "user",
                   "content": f"Classify: {txn.party} {txn.ref}, gap Rs {gap}."}])
    return res.content[0].input["label"]
from langchain_google_genai import ChatGoogleGenerativeAI
from pydantic import BaseModel
from typing import Literal

class Exception_(BaseModel):
    label: Literal["bank_charge", "short_payment", "timing_difference",
                   "missing_settlement", "duplicate", "chargeback", "other"]

classifier = ChatGoogleGenerativeAI(
    model="gemini-2.5-flash").with_structured_output(Exception_)

def classify(txn: Txn, gap: float) -> str:
    return classifier.invoke(
        f"Classify: {txn.party} {txn.ref}, gap Rs {gap}.").label
6

Propose the correcting entry, for a human to approve

A labelled exception still needs a fix, and the safe pattern is propose then approve. The model drafts the correcting journal entry in plain double-entry terms, a bank charge debited to charges, a short payment held in a suspense account, so a person sees exactly what would post and signs off before anything touches the books. The agent never posts an exception on its own. The tabs show the same draft three ways.

from google import genai
client = genai.Client()

def propose_entry(txn: Txn, label: str, gap: float) -> str:
    return client.models.generate_content(
        model="gemini-2.5-flash",
        contents=[f"Propose a one-line double-entry journal to clear a "
                  f"'{label}' of Rs {gap} for reference {txn.ref}. "
                  f"Format exactly: Dr <account> / Cr <account> Rs <amount>."]).text.strip()
# Output is a suggestion shown to a reviewer, not something that posts itself.
from anthropic import Anthropic
client = Anthropic()

def propose_entry(txn: Txn, label: str, gap: float) -> str:
    res = client.messages.create(model="claude-opus-4-8", max_tokens=120,
        messages=[{"role": "user", "content":
            f"Propose a one-line double-entry journal to clear a '{label}' of "
            f"Rs {gap} for reference {txn.ref}. "
            f"Format: Dr <account> / Cr <account> Rs <amount>."}])
    return res.content[0].text.strip()
from langchain_google_genai import ChatGoogleGenerativeAI
llm = ChatGoogleGenerativeAI(model="gemini-2.5-flash")   # or ChatAnthropic(...)

def propose_entry(txn: Txn, label: str, gap: float) -> str:
    return llm.invoke(
        f"Propose a one-line double-entry journal to clear a '{label}' of "
        f"Rs {gap} for reference {txn.ref}. "
        f"Format: Dr <account> / Cr <account> Rs <amount>.").content.strip()
7

Post the confident matches, queue the rest

Now act on the split. Matches that cleared exactly or scored above the gate are marked reconciled with their reference and the source, all auditable. Everything else, the low-confidence pairs and the labelled exceptions, lands in a review queue with its suggestion, its confidence, and the proposed entry, so a person works a short list instead of the whole statement. Nothing ambiguous is ever closed automatically. Deterministic, and the same in every framework.

Python (post or queue)
def settle(matched, review, auto_post: bool = True) -> dict:
    posted = 0
    for (m, t) in matched:                       # exact or above the gate
        if auto_post:
            ledger.mark_reconciled(m, t)         # within tolerance, written to audit
            posted += 1
    for (t, suggestion, score) in review:        # below the gate or an exception
        review_queue.add(t, suggestion=suggestion, confidence=score)
    return {"posted": posted, "to_review": len(review)}
# A clean run posts the obvious and hands a human only what truly needs a decision.
8

Assemble the run, with idempotency and tolerance guardrails

The pieces run on a schedule over each day's sources. ADK composes the matcher and the classifier under a SequentialAgent and a Runner, the Claude SDK runs them in a tool-use loop, and LangGraph builds an explicit match-then-classify graph. Whichever you pick, the same guardrails apply: key every source row on an idempotency token so a re-run never reconciles the same line twice, hold the amount tolerance and the auto-post ceiling as config rather than guesses, never auto-post an exception, and write every match, posting, and override to an immutable audit log while tracking match rate and leakage caught.

from google.adk.agents import SequentialAgent
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService

recon_pipeline = SequentialAgent(name="recon_pipeline",
    sub_agents=[matcher, classifier])          # each an LlmAgent with the tools above
runner = Runner(agent=recon_pipeline, app_name="recon",
                session_service=InMemorySessionService())

def guard(row: Txn, gap: float, ceiling: float = 5000) -> str | None:
    if ledger.seen(row.ref):        return "already_reconciled"   # idempotent
    if abs(gap) > ceiling:          return "needs_human"          # too big to auto-post
    return None
# Run nightly over the day's gateway, bank, and ledger feeds via Cloud Scheduler.
from anthropic import Anthropic
client = Anthropic()
TOOLS = [match_tool, classify_tool, post_tool]   # JSON tool schemas

def run_recon(messages: list):
    while True:
        resp = client.messages.create(model="claude-opus-4-8", max_tokens=1024,
                                      tools=TOOLS, messages=messages)
        if resp.stop_reason != "tool_use":
            return resp
        messages.append({"role": "assistant", "content": resp.content})
        for b in resp.content:
            if b.type == "tool_use":
                out = dispatch(b.name, b.input)
                messages.append({"role": "user", "content": [
                    {"type": "tool_result", "tool_use_id": b.id, "content": str(out)}]})
from langgraph.graph import StateGraph, START, END

g = StateGraph(dict)
g.add_node("match",    lambda s: {"matched": reconcile(s["feeds"])})
g.add_node("classify", lambda s: {"exceptions": classify_exceptions(s["matched"])})
g.add_edge(START, "match")
g.add_edge("match", "classify")
g.add_edge("classify", END)
recon_pipeline = g.compile()      # invoke nightly via your scheduler

Put it together: run it end to end

Here is a runnable file. It reconciles sample orders against bank credits, shows that one order's smaller bank amount is exactly the gateway fee, and asks the model to classify a settlement that never landed. Replace the SAMPLE lists with your data. Save it as main.py and run python main.py.

Python (full runnable file, ADK)
# main.py  --  run:  python main.py
# A runnable reference: reconciles a few orders against bank credits, checks one
# gateway settlement against its expected fee, and asks the model to classify a
# missing settlement. Replace the SAMPLE lists with your data.
import asyncio
from dataclasses import dataclass
from datetime import date
from google import genai
from google.genai import types
from google.adk.agents import LlmAgent
from google.adk.runners import InMemoryRunner

client = genai.Client()                       # reads GEMINI_API_KEY

@dataclass
class Txn:
    source: str; ref: str; amount: float; on: date; party: str = ""

# ---- sample sources (replace with your ledger, bank, and gateway data) ----
LEDGER = [Txn("ledger", "ORD-1", 10000.0, date(2026, 6, 1), "Acme"),
          Txn("ledger", "ORD-2",  5000.0, date(2026, 6, 1), "Globex"),
          Txn("ledger", "ORD-3",  8000.0, date(2026, 6, 2), "Initech")]
BANK   = [Txn("bank", "ORD-1", 9764.0, date(2026, 6, 2), "RAZORPAY"),  # net of fee + GST
          Txn("bank", "ORD-2", 5000.0, date(2026, 6, 3), "Globex")]    # ORD-3 never landed
# --------------------------------------------------------------------------

def reconcile(tol: float = 1.0) -> dict:
    """Exact match on reference + amount; report matched, unmatched, missing."""
    by_ref = {t.ref: t for t in LEDGER}
    matched, unmatched = [], []
    for b in BANK:
        m = by_ref.get(b.ref)
        (matched if m and abs(m.amount - b.amount) <= tol else unmatched).append(b.ref)
    landed = {b.ref for b in BANK}
    missing = [t.ref for t in LEDGER if t.ref not in landed]   # booked, no money in
    return {"matched": matched, "unmatched": unmatched, "missing_settlement": missing}

def check_settlement(gross: float, net: float,
                     fee_rate: float = 0.02, gst: float = 0.18) -> dict:
    """Expected net = gross - fee - GST on the fee. A zero gap means it was just the fee."""
    fee = round(gross * fee_rate, 2)
    expected = round(gross - fee - fee * gst, 2)
    return {"expected_net": expected, "actual_net": net, "gap": round(net - expected, 2)}

def classify_gap(detail: str) -> str:
    """Ask the model what kind of exception this is (closed label set)."""
    labels = ["bank_charge", "short_payment", "timing_difference",
              "missing_settlement", "duplicate", "chargeback", "other"]
    return client.models.generate_content(
        model="gemini-2.5-flash",
        contents=[f"Classify this reconciliation exception into one of {labels}. "
                  f"Reply with the label only. Detail: {detail}"]).text.strip()

agent = LlmAgent(
    name="recon_agent", model="gemini-2.5-flash",
    instruction=("Call reconcile and report matched, unmatched, and missing "
                 "settlements. For ORD-1 call check_settlement(gross=10000, net=9764) "
                 "to show its bank amount is only the gateway fee, not a problem. "
                 "Then call classify_gap for the missing ORD-3 settlement. "
                 "Summarize what is fine and what a human must chase."),
    tools=[reconcile, check_settlement, classify_gap])

async def main():
    runner = InMemoryRunner(agent=agent, app_name="recon")
    session = await runner.session_service.create_session(app_name="recon", user_id="demo")
    msg = types.Content(role="user", parts=[types.Part(text="Reconcile and report exceptions.")])
    async for event in runner.run_async(user_id="demo", session_id=session.id, new_message=msg):
        if event.is_final_response():
            print(event.content.parts[0].text)

if __name__ == "__main__":
    asyncio.run(main())

With a valid key, the agent reconciles ORD-2, uses check_settlement to show ORD-1's smaller bank amount is exactly the fee plus GST (a zero gap), and flags ORD-3 as a missing settlement to chase. The summary is model-generated, so the exact wording varies. Live gateway, bank, and marketplace feeds need their own credentials, so they stay in the build steps above rather than this local demo.

The reconciliation console

A reviewer sees one screen: the exceptions queue, the transaction in question, the suggested match with its confidence, and a single action to take.

Reconciliation EXCEPTIONS (3) ORD-3 Initechmissing settlement HDFC 0712bank charge, Rs 590 Amazon payoutshort, Rs 1,240 TRANSACTION ReferenceORD-3 In ledgerRs 8,000 In banknot received Expected by2 days ago SourceRazorpayLabelmissing_settlement SUGGESTION Best matchnone found Confidence0.0 Proposedhold in suspense Chase Razorpay for the payout Approve Snooze

Build, buy, or no-code

Approach Best for Effort Cost Control
Custom agent (build in-house) You reconcile across several sources, gateway, bank, and marketplace, with your own rules, and want the data in-house High Build cost + API and model usage Full
Recon feature in accounting or recon software (Tally, Zoho Books bank rules, marketplace recon tools) Standard single-source bank reconciliation, fast start Low Subscription per user or volume Medium
No-code (n8n, Make, Zapier) Low volume, one or two sources, a quick pilot Low Cheap, but brittle once the matching logic grows Low

Data protection and audit

Reconciliation touches your most sensitive records, so control of the data is part of the design. Bank statements and settlement reports carry personal data in line with the DPDP Act 2023, whose core obligations phase in through 2027, and payment data is kept in India in line with the RBI storage directive. You can run the whole pipeline in your own cloud or on-premise, so account numbers and transaction histories never leave infrastructure you control.

Every match, every posting, and every override is written to an immutable audit log with the reference, the amount, the confidence, and who approved it. That trail is what makes an automated reconciliation defensible to your auditor, and it is why nothing ambiguous, and nothing above your auto-post ceiling, is ever closed without a person.

The numbers

These are directional, not promises, and depend on your volume and how many sources you reconcile. The point is the shape of the change: less time, less leakage, full coverage, fewer errors.

Metric Manual Agent
Time spent at month-end close Days of an accountant's month spent matching rows across statements by hand Matched continuously, so the close is shorter and people review exceptions, not every line
Money lost to leakage Fee overcharges, short settlements, missed marketplace credits, and duplicate payments slip past unnoticed Every payout checked against the net it should have been, so leakage surfaces the same week, not at audit
Coverage On high-volume days, transactions get sampled rather than fully reconciled Every transaction is matched or queued with a confidence score, so nothing is skipped
Errors and rework Manual matching mis-keys and double-books under time pressure Deterministic matching within set tolerances, with an audit trail behind every decision

Gateway fee and settlement-cycle norms reflect public pricing from Razorpay, PayU, and Cashfree; payment-data storage follows the RBI directive. Figures are ranges that vary by business, not guarantees.

What makes it fail

  • !Auto-closing fuzzy matches with no confidence gate, so a wrong match reaches the books.
  • !No idempotency, so a re-run reconciles the same line twice.
  • !Treating normal T+1 settlement delay as a missing payout, drowning the queue.
  • !Ignoring GST on the gateway fee, so legitimate settlements get flagged as short.
  • !Letting the model post entries directly instead of proposing them for approval.

A safe rollout

  1. Start read-only: match one source pair (bank and ledger) and just report exceptions.
  2. Add the gateway settlement check and the model classification, still review-only.
  3. Turn on auto-posting for exact matches within a tight tolerance, exceptions to a human.
  4. Add marketplaces and more entities, with match-rate and leakage dashboards and alerts.

FAQs

General FAQs

Everything you need to know about the service and how it works. Can’t find an answer? Mail us at info@galific.com

  • What does a reconciliation agent actually reconcile?
    Whatever sources your money moves through. For most Indian SMEs that means payment gateway settlements (Razorpay, PayU, Cashfree), the bank statement, marketplace payouts (Amazon, Flipkart), and your own sales ledger, plus vendor and customer statements. The agent pulls each source, maps them to one common shape, and matches across all of them, so a sale recorded in your books, settled by a gateway net of fees, and credited to your bank a day later is traced end to end.
  • How is this different from the GST reconciliation in your billing playbook?
    They solve different problems. The billing agent reconciles GSTR-2B against your purchase register for input tax credit, and matches payments to invoices for collections. This reconciliation agent is about operational money movement: proving that gateway payouts, bank credits, and marketplace settlements line up with your ledger, and catching fee overcharges, short settlements, and missing payouts. Many businesses run both.
  • How does the matching work, exactly?
    In two passes. First an exact pass keys the ledger by reference and confirms the amount agrees within a small tolerance, which clears most lines with no model and no human. Then a scored fuzzy pass looks for an open entry within an amount tolerance and a date window, rewards a matching counterparty, and produces a confidence between 0 and 1. Only matches above a gate are accepted automatically; weaker ones go to review with the suggestion attached, so a plausible but wrong match never reaches your books.
  • Will it post entries to my books automatically?
    Only the confident, within-tolerance matches reconcile automatically, and every one is written to an audit log. Exceptions are different: the model classifies them and drafts a correcting journal entry, but that is a suggestion a person approves before anything posts. Nothing ambiguous, and nothing above your auto-post ceiling, is ever closed on its own.
  • Which framework should I use: ADK, the Claude SDK, or LangGraph?
    The right one is the one your team already runs, because the agent logic is identical in all three. The code tabs show the same ingest, match, classify, and post flow in Google ADK, the Anthropic SDK, and LangGraph. ADK and LangGraph are model-agnostic, so you can run Gemini or Claude under either; the matching maths is the same deterministic Python regardless of the framework.
  • How does it catch payment gateway fee overcharges?
    By recomputing the net it expects for every settlement. A gateway keeps a fee and charges GST on that fee, so the agent calculates gross minus fee minus GST on the fee and compares it to what actually landed. A zero gap means the difference was just the fee and the order is fine; a negative gap is a short settlement or a fee charged at the wrong rate, flagged the day the payout arrives rather than discovered months later at audit.
  • Does it work with Razorpay, PayU, Cashfree, Amazon, Flipkart, and my bank?
    Yes. Where a source has an API, the agent pulls settlements and credits directly. Where it does not, it parses the settlement report or bank statement (CSV, Excel, or MT940) instead of anyone re-keying it. Each source only needs a small column map to become the common transaction shape the matcher uses, so adding a new gateway or marketplace is a few lines, not a rebuild.
  • What happens to transactions it cannot match?
    They go to a human review queue, never silently dropped or guessed. Each item carries the best suggested match, its confidence score, the model's classification (a bank charge, a timing difference, a missing settlement), and a proposed correcting entry. A reviewer works a short, ranked list of genuine exceptions instead of scanning the whole statement, and their decision is logged.
  • How does it handle timing differences from T+1 or T+2 settlements?
    With a date window. Because a gateway settles a day or two after the sale, the fuzzy pass matches within a configurable window rather than demanding the same date. A credit that is expected but has not arrived yet sits in a pending bucket and is only raised as a missing settlement once the window has passed, so normal settlement delay never looks like a problem.
  • Is my payment and bank data safe and compliant?
    It is built to keep sensitive financial data under your control. Personal data on statements is handled in line with the DPDP Act 2023, payment data is stored in India in line with the RBI storage directive, and you can run the whole pipeline in your own cloud or on-premise. Every match, posting, and override is written to an immutable audit trail, which is exactly what a reconciliation process needs to be defensible.
  • How accurate is it, and what if it gets something wrong?
    The matching itself is deterministic: a reference and an amount within tolerance either agree or they do not, which is testable and repeatable. The model only classifies leftovers and drafts entries; it never posts silently. With a confidence gate, a human-review queue, and an audit trail behind every decision, a wrong suggestion is caught at review rather than buried in your ledger, and you can tune the tolerances and the gate to your own risk appetite.
  • Should I build a custom agent or buy reconciliation software?
    Buy a tool if your need is standard single-source bank reconciliation and you want it quickly. Build, or have it built, when you reconcile across several sources, gateway, bank, and marketplace, need your own tolerances and rules, and want the data in-house. Plenty of teams do both, using built-in bank rules for the simple cases and a custom agent for multi-source matching. See the comparison table above.
  • What does it cost to run, and how fast is payback?
    Running cost is mostly model and API usage plus hosting, which is modest next to what reconciliation recovers: fee overcharges and short settlements caught early, duplicate payments stopped, and days of month-end close handed back to your team. Because the agent checks every payout rather than a sample, the leakage it surfaces in the first few cycles often covers the build.

Want this built and running in your stack?

Galific designs, builds, and runs reconciliation agents like this on ADK, the Claude SDK, or LangGraph, wired into your gateway, your bank feed, your marketplaces, and your ledger. Or explore the ready-made versions, from Payment Recon to Bank Reconciliation, in our agent suite.