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 every package reaches an underwriter with its exceptions already flagged on it. 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 human-reviewed decision gate (Stage 6 — every package reaches L1 Review in this build), an Underwriter persona gating the review stages, an output-package export, and an underwriting overview page. You’ll use. Collections & schema (D1) · Taxonomy, lifecycle, tags & events (D3) · Ingestion & connectors (D4) · Your first agent (A3) · Teams & mesh (A11) · Guard rails (A8) · Access & roles (G1) · Shaping the experience (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 brings every package to an underwriter with its exceptions already flagged on it. Budget about 75 minutes for the single-agent happy path; two hours with the Mesh upgrade and the overview page.

Watch out — field names must be unique. A field name has to be unique across every Learner in the collection. If a Label does not appear in the tree after Add, the name is already in use somewhere else in this collection — rename it and add it again.

Steps

Stage 1 — Collection and schema

Create the collection. Studio ▸ Data ▸ Collections → + Collection at the bottom of the list.

  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.

If you don’t see a Purpose switch. Knowledge collections are switched off for your workspace — new collections are Processing anyway, which is what this build wants. Purpose is immutable after create, so pick deliberately when you do see it.

Checkpoint. Loan Underwriting appears in the collection list with the tab strip General · Taxonomy · 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 Taxonomy tab → footer Taxonomy:

  1. Taxonomy Name: Application Package
  2. Description: Loan-level and per-document fields
  3. Type: Section Classification — this Learner pulls several independent values per document, which is an extraction job. Watch out: Document Classification (or Page Classification) makes the agent a single-label classifier — its Labels are categories, and it marks the whole document with exactly one of them (the way Triage works in Build: Support intake triage). That’s the wrong shape for a Learner meant to fill many independent fields at once — the two classification types are single-label, against Section Classification’s many-fields extraction.
  4. Entity: Loan Underwriting (pre-filled). Click Submit.

A document-type classifier, for later. In this build the agent writes each document’s type into the plain doc_type field of Application Package (below). A separate single-label Document Type Learner is what a dedicated, trainable classifier needs — create it now so it is ready, or skip these two steps:

  1. Taxonomy Name: Document Type. Description: Which kind of document this is. Type: Document Classification — the agent marks the document with exactly one of these Labels, the categories themselves. Entity: Loan Underwriting. Submit.
  2. Select Document Type, + Label, and add one Label per category, mirroring how Triage’s Labels are its queues (uc-triage): Application Form, Financial Statement, Tax Return, Bank Statement, Collateral Valuation, KYC Document.

Watch out — one agent, one Learner. An agent’s Output tab wires structured output to one Learner at a time. Stage 3’s single Underwriting Document Processor targets Application Package only; it still decides each document’s type as part of its reasoning (the Stage-3 instructions below ask it to), but writes that as a plain doc_type Label on Application Package, not as a classification against the Document Type Learner — that second Learner is for when you want a dedicated, trainable classifier (see Make it production-worthy), built as its own agent the way Triage Classifier is built.

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
doc_type Which document this is: application form, financial statement, tax return, bank statement, collateral valuation, KYC document Plain text — the agent writes one of these values into it
borrower Borrower's legal name Plain text
loan_amount Requested loan amount View Config → numeric rule; tick Enable Total Field
dscr Debt-service coverage ratio (computed) View Config → numeric
ltv Loan-to-value ratio (computed) View Config → numeric
revenue Total revenue from the financial statement View Config → numeric
net_income Net income from the tax return View Config → numeric
stmt_balance Ending balance on the bank statement View Config → numeric
collateral_value Appraised value of the pledged collateral View Config → numeric
extraction_confidence Overall confidence (0–1) in this document's classification and fields View Config → numeric
review_call approve = clean and confident; decline = looks wrong; needs-info = can't resolve from this document alone View Config → single-select listing approve, decline, needs-info

extraction_confidence and review_call carry the agent’s own read on each document, for the underwriter to see — they are not a straight-through gate (Stage 6 explains why), and every package in this build lands in L1 Review for a person to confirm. Every validation option is catalogued in the Schema field reference (R3).

Scale note. A real 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.

Stage 2 — Ingestion

Where. Studio ▸ Data ▸ Ingestion. 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 Underwriting → Submit.

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 connection → Save connector.
  2. + Job → Step 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.

What happens to a package on arrival. An application package is usually many documents in one PDF. The platform splits the package into per-document units and straightens the 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 ▸ Agents ▸ Agent Toolkit ▸ 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. Finally, report an overall extraction_confidence (0–1) and a review_call: approve for a clean, confident document, decline for one that looks wrong, needs-info for anything you can’t resolve.

  2. Model: pick your project’s language 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. Now turn on Auto-annotate document as well. They are two independent toggles doing two different jobs: Structured output shapes what the agent answers, Auto-annotate is what writes that answer back onto the document and hands the document to the lifecycle. Leave Auto-annotate off and the answer never reaches the document — its fields stay empty and it never leaves its first stage. With it on, every document gets its fields and reaches L1 Review: extraction_confidence and review_call live as Labels on this taxonomy, and the auto-approve gate reads neither — it reads a plain top-level verdict this recipe cannot also produce. So an underwriter reads those two fields and decides; that is the real, working design here, not a gap to fix 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. Watch out — with Auto-annotate on this is not a side-effect-free preview: the run writes the fields onto that real document and can move it through the lifecycle, the same as a live arrival. Pick a document you don’t mind moving.

Assign it as the collection’s Worker. Studio ▸ Data ▸ Collections ▸ Loan Underwriting → Agents 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 Reader — D1).

If the Playground works but the assigned agent stalls at 0% on a real document, check that the agent is bound to a model your project can actually reach — Studio ▸ Agents ▸ Agent Toolkit ▸ Language Models lists the models registered here and which one is the default (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 ▸ Agents ▸ Agent Toolkit ▸ Agents → + Agent ▸ Mesh. 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 (shown as awaiting service) 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, run in turn — so enrich and screen each get their own queue out of validate, which is what runs them in parallel; one member with two incoming queues = fan-in (approve runs once per arrival, and decides only on the arrival that completes the set — bm.mesh_gather_when_complete).

Click Save Mesh, giving it a name that is unique across the whole platform, not just this project — for example 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 — a collection can be worked by a Mesh, an agent or an XFlow, and the Agents tab is where you say which.

The run record. Each mesh run is recorded as a durable trace: per-segment timestamps, baton hops, member states. You don’t build it; running the mesh produces it, and it is the inference-level companion to the document’s own audit trail.

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 the validate member’s own logic in the Mesh, or as validation rules on the Labels themselves. Not as a Conditional automation on an intermediate stage: the write-back hands each document straight to L1 Review, so a Conditional parked between the two never fires. Detail: D3.

The sanctions/fraud screen as a guard rail. A guard rail is a named policy object attached to the agent (A8). Studio ▸ Agents ▸ Agent Toolkit ▸ 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, set review_call to decline, and never recommend approve — route to human review.” Set decline here regardless — it is the honest instruction to give the agent, and it’s the human reviewer reading review_call on the package who acts on it: every package already reaches the Underwriter-gated L1 Review in this build (Stage 6), fraud flag or not.
  4. Save. Then attach it: edit Underwriting Document Processor (or the Mesh’s approve agent) → Governance tab → Guard rails ▸ + Add → pick Sanctions & fraud screen → Save Agent.

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 model 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: human review for every package

Clicks. Studio ▸ Data ▸ Collections ▸ Loan Underwriting → Lifecycle tab. The 8-stage Four-Eyes flow is already seeded; you confirm the auto-approve gate stays off, gate the review stages, and confirm routing (D3).

  1. Below the stage graph is the Auto-decide policy card — the platform’s straight-through gate for an agent that reports a plain top-level verdict. This build’s agent doesn’t: extraction_confidence and review_call are Labels, which the card does not read. Leave the card switched off (Auto-decide is OFF) — it has nothing to act on here, and a live sanctions/fraud decision should never be wired to it anyway. Every package reaches L1 Review, gated to the Underwriter persona you create below, by design. Two things worth knowing before you ever feed the card (the recipe that does is B3’s — unverified, so check it on a live project first): Require zero open flags is on by default and holds a document with any value still marked as a problem and not cleared — a mark someone raised on it, a quality check it failed, a validation rule it broke — for review, however confident the agent was; and a Max amount ($) cap reads the field you name in Amount field name from the agent’s reply, sending a document to review when that field is missing or not a number (B3 covers this in full).
  2. Click the L1 Review stage → confirm Send to Inbox (human review) is on; same for L2 Approval. Seeded on — you are verifying, not adding.

Give the underwriter access — the governance step this build cannot skip. A credit decision must be gated to the people who are allowed to make it:

  1. Go to Studio ▸ Governance ▸ Users & Access ▸ Personas and click + Add persona. Name it Underwriter, choose Collections = Loan Underwriting, choose Stages = L1 Review and L2 Approval, and add the people who hold it. Save.
  2. Back on the Lifecycle tab, set each of those stages’ Can be viewed by to Underwriter. Only its holders can now see and act on a package resting in those stages; leave the setting empty and everyone in the project can. Full tour, including the protected Project Admin role that runs the project itself: Access & roles (G1).

Watch out. Don’t use Reset to default on a lifecycle with live documents — it is destructive. And deleting a persona removes its stage gates too, which quietly re-opens those stages to everyone — re-check your gates after any persona change.

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 shows the agent as the actor; a human one shows the person by name (G5). On the Mesh path, the run record from Stage 4 is the inference-level companion.

Stage 7 — Review, decide, export

What an underwriter does. Every package enters L1 Review — gated to the Underwriter persona you created in Stage 6 — and an Inbox item wraps it:

  1. Consumer app ▸ Inbox. Each card shows a headline (Review and the package’s title), a provenance line (wraps ▸ Loan Underwriting · L1 Review), assigned when someone holds it, and its age — not what the agent flagged; that is on the package itself. Every package is in this queue (Stage 6); the flags on its Document Detail are what tell the underwriter where to look first. If an item isn’t in the Inbox yet, refresh it: for a package moved automatically, the item is added when the Inbox list loads, or by a background pass within about five minutes. The Inbox doesn’t act — read, snooze, dismiss only (E5).
  2. Click Open → the Document Detail page — 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 ▸ Data ▸ Export → + 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 review — Loan Underwriting, Stage Approved, + Add Action → Webhook to your endpoint (D3).

Stage 8 — The underwriting overview page

Clicks. Studio ▸ Experience ▸ Designer ▸ Pages → create a page, choose a layout, and place four cards from the card library (E7). Configuring a card is: place it → point it at your data → set its options → save. Four tiles:

  • Packages — a document-count card (total ingested this period).
  • Approved rate — a ratio: Approved ÷ total decided. Every package passes through a human in this build (Stage 6), so this tracks how many clear review, not an auto-advance rate.
  • Pending — a document-count card 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 card fits? Studio ▸ Experience ▸ Designer ▸ Cards browses the whole library — every card type you can place, what it shows, and what it needs.

Checkpoint. Preview the page to see exactly what your credit-ops users get. If you cannot see Pages at all, page design is switched off for your workspace — ask your administrator.

Stage 9 — Test it end to end

About fifteen minutes:

  1. Upload. Studio ▸ Data ▸ Ingestion → Upload → one clean application-package PDF and one messy one → Target collection = Loan Underwriting → Submit. 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%? Processing hasn’t started, or the agent is bound to a model the project can’t reach — see the Stage 3 note.
  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. Every package — clean or flagged — reaches L1 Review for the Underwriter persona to confirm; this build has no auto-advance path (Stage 6). Don’t tell anyone downstream to expect a clean package to skip review.
  5. Review it. Sign in as someone holding the Underwriter persona, open the Inbox item → Open → verify a flagged field against its highlighted source, correct one value, Rescore if DSCR/LTV shift, approve through L2 Approval → Approved. The Inbox item disappears.
  6. See it exported. The Approved package triggered your Underwriting output package export.
  7. Read the overview page. Packages ticks up, Approved rate reflects the file(s) you pushed through to Approved, 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 build an Exceptions View from the Loan Underwriting Documents list — columns Borrower, Loan Amount, DSCR, Flag, Status — and Options ▸ Save View as (Save Under = Project Views — E7); Studio ▸ Data ▸ Views is where you find it afterwards, not where you build or scope it — Allowed Roles lives on the Documents list’s Options menu. A DocList card has no “point at a View” setting, so to show the same slice on the overview page, place a DocList card, add Borrower, Loan Amount, DSCR and Flag as Label columns (the fixed Columns list is built-ins like title, stage and updated, not Label values — take the stage column from there, not from the repeater) and set its Label filter to match. Check first that the filter is one a card can express: a Label filter row compares values as numbers or matches them as text, and offers no date operator (E7).
  3. Cost tracking. Track cost per application, document, stage and field, and watch for spikes — the consumption side of Studio ▸ Governance ▸ Oversight ▸ Audit & Usage (G5).
  4. A second model. Build a second extraction agent on a different language model 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