Academy · Solutions · Agentic Workflow

Build: Loan underwriting

In one line. A borrower’s application package — one fat PDF bundling the application form, financial statements, tax returns, bank statements, a collateral valuation — gets classified document by document, extracted per type, validated within and across documents, screened against sanctions and fraud, and the clean files sail straight through while the exceptions queue for an underwriter. You’ll build. A multi-document Processing pipeline: a Loan Underwriting collection, one classify-then-extract agent (upgradeable to a five-member Mesh), validation rules plus a sanctions/fraud guard rail, a 0.99-confidence straight-through-processing gate, an output-package export, and an underwriting dashboard. You’ll use. Collections & schema (D1) · Lifecycle, tags & events (D3) · Ingestion (D4) · Your first agent (A3) · Teams & mesh (A11) · Guard rails (A8) · Dashboards & views (E7)

This is B3’s invoice loop scaled up: instead of decide on each document, you decide on each application package made of many documents. A classifier works out what each document is, an extractor pulls the fields that type owns, rules reconcile the numbers across documents, and the lifecycle auto-advances anything clean and confident. Budget about 75 minutes for the single-agent happy path; two hours with the Mesh upgrade and the dashboard.

Watch out — build this in a fresh project. Underwriting taxonomies are big: one classification field plus dozens of extraction fields. On a production loan-origination build, adding ~12 labels under a new Learner in an already-busy project surfaced only 3 of them — label names collided with Labels under other Learners. In a fresh project the same bulk-add surfaces reliably. Create a clean project named Loan Underwriting before Stage 1. If a Label ever fails to appear in the tree after Add, you are in a busy project — move.

Steps

Stage 1 — Collection and schema

Create the collection. In your fresh project: Studio ▸ Data Model ▸ Collections+ Collection (rail footer).

  1. Purpose: Processing (the default). Application packages move through a decision.
  2. Collection Name: Loan Underwriting
  3. Description: Borrower application packages for classification, extraction, validation and credit decision
  4. Leave every other accordion at its default. Click Save.

Flag-gated. The explicit Processing/Knowledge switch sits behind the knowledgeCollections flag — if you don’t see a switch, new collections are Processing anyway. Purpose is immutable after create, so pick deliberately.

Checkpoint. Loan Underwriting appears in the left rail with the tab strip General · Schema · Lifecycle · Tags · Events · Ingestion · Agents. The Lifecycle tab is already seeded with the 8-stage Four-Eyes flow — you tune it in Stage 6.

Create the Learner. Open the Schema tab → footer Taxonomy:

  1. Taxonomy Name: Application Package
  2. Description: Document type + loan-level and per-document fields
  3. Type: DocumentClassification (whole-document classification + field extraction)
  4. Entity: Loan Underwriting (pre-filled). Click Submit.

Add the classification field first. Select Application Package, then + Label. This field holds what kind of document this is:

Label Name Description Type
doc_type Which document this is: application form, financial statement, tax return, bank statement, collateral valuation, KYC document Plain text — the classifier writes one of these values into it

Add the loan-level and per-document fields. + Label for each (Add between, Add and exit on the last). A field’s type comes from a validation rule under View Config plus Record Config — there is no single “type” dropdown (D1):

Label Name Description Type
borrower Borrower's legal name Plain text
loan_amount Requested loan amount View Confignumeric rule; tick Enable Total Field
dscr Debt-service coverage ratio (computed) View Confignumeric
ltv Loan-to-value ratio (computed) View Confignumeric
revenue Total revenue from the financial statement View Confignumeric
net_income Net income from the tax return View Confignumeric
stmt_balance Ending balance on the bank statement View Confignumeric
collateral_value Appraised value of the pledged collateral View Confignumeric

Every validation option is catalogued in the Schema field reference (R3).

Under the hood. A production underwriting taxonomy easily carries ~30 Labels across classification, document-intelligence and data-transform groups. You are building a teaching subset; the shape — one classification field, many extraction fields, confidence on each — is identical. Add the rest under Make it production-worthy.

Roadmap, not a click-path. Taxonomy governance — field-dictionary versioning, a Reviewer-to-Owner approval workflow, sensitivity tiers — is designed but not a shipped configurator surface yet. Don’t promise it.

Stage 2 — Ingestion

Clicks. Studio ▸ Connector ▸ Ingestion.

Flag-gated. This teaches the modern Sources v3 screen (the ingestionV3 lab flag). If you land on the older single-source wizard, enable ingestionV3 from the Lab menu (E1). Full detail: Ingestion & connectors (D4).

The fast path (one-shot, for testing now). Click Upload (toolbar) → drag in a sample application-package PDF → set Target collection = Loan UnderwritingSubmit.

The recurring path (for production). A connector must exist before a job can use it:

  1. + Connector → pick Botminds Drive (this project’s internal drive — no credentials). Name it → Test connectionSave connector.
  2. + JobStep 1: select that Drive connector. Step 2: Job name Application packages intake; Browse… to the packages folder; Extensions pdf; tick Recursive; Target collection Loan Underwriting. Step 3: Recurring → every 1 Hours (check the Next 5 runs preview) → Create job.

Under the hood — preprocessing. An application package is usually many documents in one PDF. The platform splits the package into per-document units (each gets a UUID and content hash) and deskews pages before the classifier sees them. You don’t configure this per job. A package arriving as a folder of separate PDFs works too — each file is its own unit.

Checkpoint. The Jobs tab shows Application packages intake … active. Click Run now, then the Runs tab — the funnel counters (Enum → Fetch → New → … → Disp) fill, and clicking the run opens Run detail with a per-file log.

Stage 3 — The classifier + extractor agent

One agent that, per document, decides its type and fills the fields that type owns. Start with a single structured-output agent; Stage 4 shows when to split it.

Clicks. Studio ▸ Agent Builder ▸ Agents → footer + Agent ▸ Agent (A3).

  1. Persona: Name Underwriting Document Processor; Description Classifies each application document and extracts its fields. Use the co-pilot on Instructions, seed it with “Classify a loan-application document and extract its fields,” then refine to roughly:

    You process documents from a borrower’s loan application package. First decide the document type — one of: application form, financial statement, tax return, bank statement, collateral valuation, KYC document — and write it to doc_type. Then extract only the fields that document supports (e.g. revenue from a financial statement, net income from a tax return, ending balance from a bank statement, appraised value from a collateral valuation). Never invent a value the document doesn’t show; leave it blank instead.

  2. Model: pick your project’s LLM Model. Set Agent Mode = Thinking while you tune.
  3. Output: turn on Structured output. Pick the Taxonomy/Learner Application Package, set Process unit = Page, confirm each Label carries a short Description, and turn on Confidence Score and References. Per-field confidence is what routes a package to an underwriter later.
  4. Click Save Agent — there is no autosave.

Test in the Playground before wiring it up. In the right pane: Input Type = Document, pick a sample application document, press Enter. Watch doc_type resolve and the right fields stream in with confidence and page references. Flip Agent Mode = Fast and re-run — same result, quicker; that is your production setting.

Assign it as the collection’s Worker. Studio ▸ Data Model ▸ Collections ▸ Loan UnderwritingAgents tab → Assign Underwriting Document Processor as the intake/processing agent. Every document that lands is now run by it automatically (a Worker is not a ReaderD1).

Honesty callout — live LLM agents on a local stack. On a production build we hit real platform bugs running a live agent end to end: the bound model didn’t always stick at run time, the fallback model wasn’t deployed on the local key, and a document-attached run could throw inside the document runner. If your Playground works but the assigned Worker stalls at 0% on a real document, confirm the agent is bound to a deployed model — one your key actually serves — not a default that maps to an undeployed name. See LLMs & services (A2).

Stage 4 — Optional upgrade: the five-member Mesh

Decompose the single agent into a durable, multi-stage pipeline so each stage is tuned and tested on its own, survives a restart, and can fan out parallel checks. Optional — the Stage-3 agent already works; reach for a Mesh only when the automation is long, branching, must survive restarts, or has steps over 240 seconds (OCR on a 200-page valuation report, a slow partner screening API). Full decision guide: Teams & mesh (A11).

Clicks. Studio ▸ Agent Builder ▸ Agents+ Agent ▸ Mesh (always available — not flag-gated). This opens the XFlow designer in mesh mode; the palette is XFlow Operator + Queue + Service.

The shape to draw — five members wired by queues:

package ─► classify ─► q.a ─► validate ──┬─► enrich ───────┐
           (XFlow)            (XFlow)    │    (XFlow)      ├─► approve ─► summarize
                                         └─► screen ───────┘    (agent)     (sink)
                                             (sanctions/fraud
                                              Service, parks)
  • classify, validate, enrich are XFlow Operator nodes, each referencing a runnable.
  • screen is a Service node — it parks (awaiting) while an external screening worker does the slow check and reports back. This is the platform’s answer to the 240-second member ceiling (A2).
  • approve is an agent member that writes the decision; summarize is the sink that writes the Summary / output package.
  • A Queue box sits between members; node→queue publishes the baton, queue→node subscribes. Two members on one queue = fan-out (validate feeds enrich and screen in parallel); one member with two incoming queues = fan-in (approve waits for both).

Click Save Mesh. MeshId is global, not per-project — pick a unique id like loan-underwriting-uw. Watch a run on the Runs tab: a member timeline with per-member states (done / running / awaiting service), a queue-health banner, and a Baton trail of pub/sub hops. The awaiting service dot on the screen step means it is parked on your worker — not stuck.

Assign the Mesh as the collection’s Worker exactly as in Stage 3 — intake routes by kind (mesh → agent → xflow).

Under the hood — the run ledger. Each mesh run is recorded as a durable trace: per-segment timestamps, baton hops, member states. On a production lending build this was the immutable audit fabric. You don’t build the ledger; running the mesh produces it.

Tip. Don’t reach for a Mesh too early. If the single agent processes a package correctly and you don’t need durability, parallel screening, or a >240 s step, ship the single agent.

Stage 5 — Validation and the sanctions/fraud guard rail

Within- and cross-document validation.

  • Within-doc: the numeric rules on loan_amount, dscr, ltv (Stage 1’s View Config) reject garbage — negative, non-numeric. Set on the Label, enforced on every document.
  • Cross-doc: the reconciliation checks — does net_income from the tax return agree with the financial statement’s story? does collateral_value support the ltv against loan_amount? does dscr hold given net_income and the requested loan_amount? Express these as a Conditional automation on the Validate stage of the lifecycle, or as the validate member’s own logic in the Mesh. Detail: D3.

The sanctions/fraud screen as a guard rail. A guard rail is a named policy object attached to the agent (A8). Studio ▸ Agent Builder ▸ Guard Rails+ Guard Rail:

  1. Name: Sanctions & fraud screen
  2. Description: Flag any borrower name on a sanctions/PEP watchlist and flag anomaly patterns
  3. Instruction: “If the borrower’s name matches a sanctions or PEP watchlist, or the document shows tampering / inconsistent fonts / altered numbers, set a fraud flag and never auto-approve — route to human review.”
  4. Save. Then attach it: edit Underwriting Document Processor (or the Mesh’s approve agent) → Governance tab → Guard rails ▸ + Add → pick Sanctions & fraud screenSave Agent.

Honesty callout — a real sanctions screen is an external service. A genuine watchlist check calls a live screening API and can be slow — exactly why Stage 4 modelled it as a Service that parks, not inline agent reasoning. The guard rail sets the policy (flag, never auto-approve); the lookup belongs in a Service or Tool against your real list. The LLM does not “know” the watchlist.

Checkpoint. The agent’s Governance tab shows the Sanctions & fraud screen shield chip; validation rules show on the relevant Labels’ View Config.

Stage 6 — The lifecycle gate: STP by default, HITL by exception

Clicks. Studio ▸ Data Model ▸ Collections ▸ Loan UnderwritingLifecycle tab. The 8-stage Four-Eyes flow is already seeded; you add one Auto-Decide rule and confirm routing (D3).

  1. Below the stage graph, open the Auto-Decide policy card:
    • Enabled = true
    • Applies To = approve — auto-advance only; keep human eyes on declines.
    • Min Confidence = 0.99 — the production straight-through gate.
    • Require Zero Flags = true — any sanctions/fraud flag from Stage 5 forces a human.
    • Optional: a Max Amount so only loans under a threshold auto-advance. Note the evaluator compares the agent’s self-reported _meta.amount, not a schema field by name — the cap only fires if the Worker surfaces the loan amount in its _meta.
    • Save. The preview reads roughly “Auto-advance when AI confidence ≥ 0.99 AND zero flags.”
  2. Click the L1 Review stage → confirm Send to Inbox (human review) is on (IncludeManualIntervention); same for L2 Approval. Seeded on — you are verifying, not adding.

Watch out. Don’t use Reset to default on a lifecycle with live documents — it is destructive.

Under the hood — the audit trail is the decision. There is no separate “decision” object. The stage-move history — who advanced it, at what confidence, with what flags — is the record; an automatic advance is stamped by the auto-decide actor, a human one by name (G5). On the Mesh path, the run ledger from Stage 4 is the inference-level companion.

Stage 7 — Review, decide, export

What an underwriter does. A package that doesn’t auto-advance enters L1 Review and an Inbox item wraps it:

  1. Consumer app ▸ Inbox (route inbox). Each card shows the borrower, a provenance line (wraps ▸ Loan Underwriting · doc-… · L1 Review), age, and why it is here — low confidence, missing document, sanctions flag. The Inbox doesn’t act — read, snooze, dismiss only (E5).
  2. Click OpenDocument Detail (/<project>/:docId) — the document on one side, extracted fields on the other (E3).
  3. Read the flagged low-confidence fields first. Click a value — say DSCR : 1.08 — and the viewer scrolls to and highlights the source segment it was read from. That is how you catch a mis-read or a cross-doc mismatch.
  4. Correct a wrong value in place (the edit becomes a teaching signal — A9); Rescore if dscr/ltv need recomputing.
  5. Advance from the workflow action pane: approve → L2 Approval (second eye) → Approved. Every move writes the audit trail.

Export the output package. Studio ▸ Connector ▸ Output+ Export (D4):

  1. Name: Underwriting output package
  2. Export Type: a structured (split/bookmark) type, so the package ships as data plus provenance. A production build delivers a canonical JSON output package to the bank’s loan-origination API.
  3. Destination: Azure Blob, Botminds Drive, or a Webhook to your loan-origination endpoint.
  4. Toggle Export input document to ship the original package alongside the data. Save.

Fire it on decision. Loan Underwriting ▸ Events tab → + Event → trigger stage change, Workflow Four-Eyes, Stage Approved, + Add Action → Webhook to your endpoint (D3).

Stage 8 — The underwriting dashboard

Clicks. Studio ▸ Intelligence → select or create a dashboard → add widgets (E7). Building a widget is: add → pick template → optional chart type → point at data → save. Four tiles:

  • Packages — a Documents Count widget (total ingested this period).
  • STP rate — a ratio: auto-advanced (auto-decide actor in the audit trail) ÷ total.
  • Pending — a Documents Count filtered to L1 Review + L2 Approval.
  • Exceptions / watchlist hits — a count filtered to documents carrying a review flag or the Watchlist Hit / Fraud Flag Tag (add these on the Tags tab first).

Tip. Not sure which template fits? Use the Agentic Widget — describe the tile in plain language (“STP rate this month”) and let AI build it.

Checkpoint. Open live dashboard jumps to /_dashboard?did=<id> — the exact view your credit-ops users get. The Dashboard tab is hidden from the client role and under the DisableDashBoard flag.

Stage 9 — Test it end to end

About fifteen minutes:

  1. Upload. Studio ▸ Connector ▸ IngestionUpload → one clean application-package PDF and one messy one → Target collection = Loan UnderwritingSubmit. The result panel reports “N of M registered.”
  2. Watch it process. The document list shows a Processing… chip climbing to 100% as the Worker classifies each document and fills the fields. Stuck at 0%? The background worker isn’t running, or the agent is bound to an undeployed model — see the Stage 3 callout.
  3. See classification + extraction. Open a document; doc_type resolved per document, the right per-type fields filled with confidence and page references.
  4. See routing. The clean package auto-advances (stamped by the auto-decide actor); the messy or flagged one lands in the Inbox under Needs Review.
  5. Review it. Open the Inbox item → Open → verify a flagged field against its highlighted source, correct one value, Rescore if DSCR/LTV shift, approve through L2 ApprovalApproved. The Inbox item disappears.
  6. See it exported. The Approved package triggered your Underwriting output package export.
  7. Read the dashboard. Packages ticks up, STP rate reflects the auto-advanced file, Pending and Exceptions reflect the rest.

All seven and you have a working, audited loan-underwriting pipeline — built by configuration.

Make it production-worthy

  1. More document types and fields. Add the remaining per-doc fields (credit score, guarantor details, employment- or business-verification dates) as Labels on Application Package and matching Labels on the agent’s Output tab, then Rescore. Schema and agent must agree or the field stays empty.
  2. Exception tags and a reviewer View. On the Tags tab add Watchlist Hit, Fraud Flag, Missing Document, Income Mismatch; then define an Exceptions View (columns: Borrower, Loan Amount, DSCR, Flag, Status), Type = Project, Allowed roles = Reviewer (E7).
  3. Cost telemetry. A production build tracks cost per application / document / stage / field with a spike alert. Surface compute-cost analytics on the dashboard.
  4. A second model. Build a second extraction agent on a different LLM and swap it in — the agent’s Model tab is a single swap point, which is the proof against model lock-in (A2).
  5. Graduate to the Mesh. When you need the sanctions screen parked as a Service or credit and fraud checks fanned out in parallel, move from the Stage-3 agent to the Stage-4 Mesh.

Where to go next

Prefer learning inside the product? The same academy lives in the platform's Learn menu — every screen links to the chapter that explains it.

See the platform live