# How our Pipeline orchestrates LBO Due Diligence ?

> Source: <https://dev.to/thienban/how-our-pipeline-orchestrates-lbo-due-diligence--2ohm>
> Published: 2026-11-13 14:09:31+00:00

*Welcome to the third chapter of our four-part series, **Building Sovereign, Hallucination-Free Financial AI**. In [Part 1: Building a Deterministic Financial Agent with Dual-Persona LoRA](https://dev.to/thienban/building-a-deterministic-financial-agent-with-dual-persona-lora), we prohibited probabilistic language models from performing arithmetic. In [Part 2: How our Training Recipe led us to "Hallucination"](https://dev.to/thienban/how-our-training-recipe-led-us-to-hallucination-18bm), we stepped into the machine room: fine-tuning an 8B model.*

Today, we address the ultimate frontier: **What happens when the financial question cannot fit into a single prompt or table?** How do we orchestrate complex, multi-step workflows like a Cross-Table LBO Due Diligence across disparate financial statements—and how do we ensure the final synthesis sent to the boardroom is 100% immune to semantic inversion and hallucination?

In [Part 2](https://dev.to/thienban/how-our-training-recipe-led-us-to-hallucination-18bm), we showed that when an 8B specialist model encounters missing variables in a messy financial filing, standard RLHF conditions it to panic: it grabs loosely related numbers from adjacent rows to satisfy the prompt, falling directly into the **Compensation Hallucination Trap**.

We fixed this at the micro-level with **response-only masking** and **rejection circuit breakers**. But in enterprise finance, you quickly encounter a macro-level barrier:

Imagine handing an autonomous agent an audited annual report (like the official French standalone filing — *comptes sociaux* — of **Renault SAS**, Renault Group's main operating subsidiary) and asking:

*"Is a leveraged buyout (LBO) of this target financially feasible based on its latest audited accounts?"*

Answering this is not a one-shot retrieval problem. It requires navigating across multiple disparate accounting schedules:

`CAPACITE D'AUTOFINANCEMENT`, a proxy for pre-CapEx operating cash flow in French GAAP) in the Cash Flow Statement (`RESULTAT D'EXPLOITATION / CHIFFRE D'AFFAIRES`).
If you shove all these tables into a single context window and ask an 8B model to reason through all five steps at once, **attention anxiety returns with a vengeance**. The model conflates standalone operating subsidiary debt with consolidated group figures, divides equity by operating profit, or produces subtle, catastrophic semantic inversions.

To solve this, we must enforce **Separation of Concerns** at the system architecture level.

Instead of burdening a single specialist model with planning, document navigation, tool invocation, and narrative synthesis, we architected a **Bilateral Multi-Agent Hierarchy** governed by Domain-Driven Design (DDD).

**What is a Multi-Step DAG?** A **Directed Acyclic Graph (DAG)** is a deterministic task pipeline where execution flows strictly forward from prerequisites to dependent outputs (**Directed**) with zero infinite loops (** Acyclic**). Independent balance sheet extractions run in parallel, while cross-table ratios wait for their exact upstream inputs before converging into the final decision policy.

```
flowchart TD
    User(["💼 Boardroom Prompt:\n'Is an LBO takeover feasible?'"]) --> CrewMgr["👔 Generalist Crew Manager\n(Hierarchical Task Planner & Arbiter)"]

    subgraph BilateralLoop ["🤝 Bilateral Multi-Agent Negotiation"]
        CrewMgr <-->|"1. Evaluates Balance Sheet Rubrics"| SpecAdv["🧠 Domain Specialist Advisor\n(LboCovenantPolicyVO & Accounting Ontology)"]
        SpecAdv -->|"2. Proposes 5-Pillar Credit Plan"| DAG["⚡ Execution Plan (5-Step DAG)"]
    end

    subgraph ExecutionCore ["⚙️ Deterministic Execution Sandbox"]
        DAG --> T1["Pillar 1: Debt Service (YoY CAF)"]
        DAG --> T2["Pillar 2: Historical Deleveraging (YoY Debt)"]
        DAG --> T3["Pillar 3: Cash Flow Coverage (Debt / CAF)"]
        DAG --> T4["Pillar 4: Balance Sheet Gearing (Debt / Equity)"]
        DAG --> T5["Pillar 5: Operating Margin Cushion"]
        T1 & T2 & T3 & T4 & T5 --> PyDec["🐍 Python Decimal Runtime (IEEE-754)\n+ Physical Same-Row Grounding [p.X]"]
    end

    PyDec --> DraftSynth["📝 Executive Synthesizer\n(Draft Decision Memo)"]

    subgraph AdversarialGate ["🛡️ Adversarial GenRM Gatekeeper"]
        DraftSynth --> GenRM{"⚖️ GenRM Critic\nAdversarial Reward Auditor"}
        GenRM -->|"❌ REJECT (Polarity contradiction / Tol > 0.05%)"| SelfCorrect["Self-Correction Loop"]
        SelfCorrect -.-> DraftSynth
        GenRM -->|"✅ CERTIFIED (100% Grounded & Directionally Verified)"| FinalReport["🏆 Certified ComEx Due Diligence Memo\n(Ready for Boardroom Delivery)"]
    end
```

The **Specialist Advisor** does not write paragraphs of text. Its sole responsibility is to evaluate available physical table rubrics against declarative banking covenants:

```
@dataclass(frozen=True)
class LboCovenantPolicyVO:
    """Invariants of LBO banking feasibility (Domain-Driven Design)."""
    max_repayment_years: float = 3.5       # Standard senior debt covenant (< 3.5x)
    critical_repayment_years: float = 5.0  # Junk debt alert threshold
    max_gearing_pct: float = 100.0         # Max Debt / Equity ratio
    min_operating_margin_pct: float = 3.0  # Operating safety buffer

    def evaluate_deal_feasibility(
        self, repayment_years: float, gearing_pct: float, margin_pct: float
    ) -> tuple[str, str]:
        if repayment_years > self.critical_repayment_years or gearing_pct > self.max_gearing_pct:
            return "UNFAVORABLE", "Debt repayment horizon exceeds banking covenants."
        if repayment_years > self.max_repayment_years:
            return "RESERVED", "Requires mezzanine financing or subordinated vendor loan."
        return "FAVORABLE", "Robust cash flow profile meets senior debt underwriting criteria."
```

When the Generalist Crew Manager receives the LBO query, it feeds the extracted table headers to the Specialist, which compiles an **`ExecutionPlanDTO`** composed of 5 non-negotiable credit pillars:

`compute_pct_change`.` DETTES FINANCIERES Note E.4`). Tool: `compute_ratio(scale="RAW")`.` CAPITAUX PROPRES Note E.1`). Tool: `compute_ratio(scale="PERCENT")`.` RESULTAT D'EXPLOITATION`) / Revenue (` CHIFFRE D'AFFAIRES Note C.1`). Tool: Because each task has designated keywords and an explicit formula, the tasks execute sequentially or in parallel against our **Physical Grounding Engine**.

As established in [Part 1](https://dev.to/thienban/building-a-deterministic-financial-agent-with-dual-persona-lora), all tool calls are executed in Python using exact `Decimal` arithmetic. 

In our Renault SAS 2024 benchmark test ([`LOGS_ECHANGE_GEN_SPEC_LBO_RENAULT.md`]), the runtime produces audited outputs anchored to exact physical lines:

```
{
  "metric_label": "Ratio de couverture Dette / CAF (années)",
  "tool_name": "compute_ratio",
  "operands": {
    "numerateur": 10587000.0,
    "denominateur": 1245000.0
  },
  "resultat_brut": 8.503614457831325,
  "resultat_arrondi": 8.5,
  "unite": "ANNEES",
  "citation": "[p.42, Note E.4 & p.40, TAF]"
}
```

The arithmetic outcome is definitive: **8.50 years of cash flow** are required to extinguish existing debt. According to `LboCovenantPolicyVO` ($> 5.0\text{ years}$), an LBO without massive capital restructuring is **strictly unfeasible**.

Now comes the ultimate vulnerability: **How do we guarantee the LLM does not distort this finding when writing the final executive briefing?**

Most agent frameworks rely on the LLM's own self-critique (e.g., *"Review your previous response and fix any errors"*). As research has repeatedly demonstrated, this triggers **sycophancy and confirmation bias**: the model rationalizes its mistakes rather than catching them.

**What does GenRM stand for?** **GenRM** stands for **Generative Reward Model**—an adversarial verifier pattern introduced in [*Generative Verifiers: Reward Modeling as Next-Token Prediction* (arXiv:2408.15240)](https://arxiv.org/abs/2408.15240). While frameworks like NVIDIA's post-training pipeline for *Nemotron-3.5-Bolt* use GenRM at **train time** (RLHF/RLVR alignment), we apply the same generative verifier pattern at **inference time** inside a **2-Layer Hybrid Neuro-Symbolic `GenRMCritic`** with absolute veto power before any memo reaches the C-suite.

To achieve true enterprise readiness without relying on fragile regexes alone—and without wasting GPU cycles on obvious arithmetic errors—we architected **`GenRMCritic`** as a two-stage neuro-symbolic gatekeeper:

``` php
flowchart LR
    Draft["Draft Executive Memo"] --> Layer1["Layer 1: Symbolic Pre-Filter\nExact Metric Tolerance <= 0.05%"]
    Layer1 -->|"Fail"| Rej1["❌ Instant VETO (0 LLM calls):\nUncertified number found"]
    Layer1 -->|"Pass"| Layer2["Layer 2: Generative GenRM-CoT Judge\nPolarity, LBO Verdict & Consistency"]
    Layer2 -->|"VETO"| Rej2["❌ VETO (GenRM-CoT):\nSemantic / Verdict Contradiction!"]
    Layer2 -->|"PASS"| Cert["🏆 CERTIFIED FOR BOARDROOM"]
```

Every percentage extracted from the draft memo must match a certified result in the `FinancialRuntime` registry within a $0.05\%$ margin. If the model hallucinates *"an operating margin of 42.5%"* when the certified runtime output was `-18.47%`, Layer 1 issues an immediate VETO with **zero LLM calls**.

The most insidious failure in financial AI is **directional polarity inversion** or **strategic verdict contradiction** expressed with unexpected phrasing.

Consider these real adversarial cases verified against live Nemotron 3.5 Bolt in our E2E benchmark:

`-18.47%`."* -> Layer 2 (` GenRM-CoT`) issues `[VETO]` because `LboCovenantPolicyVO` imposes `UNFAVORABLE`, a draft claiming Here is our production 2-layer `GenRMCritic` implementation (with an offline deterministic fallback when no GPU/model backend is injected):

```
class GenRMCritic(GenRMCriticProtocol):
    """2-Layer Hybrid Neuro-Symbolic Auditor (Symbolic Pre-Filter + GenRM-CoT)."""

    def __init__(self, model_backend: ModelBackendProtocol | None = None) -> None:
        self._model = model_backend

    def audit_synthesis(
        self,
        draft_synthesis: str,
        certified_results: list[CertifiedFinancialResultDTO],
        expected_deal_verdict: str | None = None,
    ) -> tuple[bool, str]:
        if not draft_synthesis or not draft_synthesis.strip():
            return False, "Rejet GenRM : la note de synthèse est vide ou non rédigée."
        # Layer 1: Pure Symbolic Pre-Filter (0 LLM calls on numeric mismatch)
        if certified_results:
            validation_ok, reason = self._audit_percentages(draft_synthesis, certified_results)
            if not validation_ok:
                return False, reason
        # Layer 2: Generative GenRM-CoT Judge (dynamic semantic & verdict audit)
        if self._model is not None:
            return self._audit_with_genrm_cot(draft_synthesis, certified_results, expected_deal_verdict)
        # Offline deterministic fallback for CI unit tests without a live LLM
        return self._audit_deterministic_fallback(draft_synthesis, certified_results, expected_deal_verdict)
```

When a VETO is issued, the Generalist Crew Manager initiates a single targeted correction turn, injecting the explicit rejection reason into the synthesizer's context.

When all gates clear, the engine outputs an auditable, executive-ready memorandum. Below is the **actual verbatim output (`synthese_complete`)** generated end-to-end by our live Nemotron 3.5 Bolt pipeline + Python IEEE-754 runtime + Hybrid Neuro-Symbolic `GenRMCritic` on **Renault SAS's audited 2024 standalone accounts** (*comptes sociaux*, `elapsed_s: 104.08s`, `certified_count: 4`, `genrm_calls: 2`, GenRM verdict: `[PASS]`):

```
# NOTE DE SYNTHÈSE FINANCIÈRE EXÉCUTIVE (ComEx)

> **Statut d'Audit** : 🟢 CERTIFIÉ CONFORME | **Réf. Document** : `5f8186cfdca20e93...` | **Date** : 13/11/2026 13:36 UTC

> **Verdict Covenants LBO** : `FAVORABLE` — Profil de génération de cash-flow et levier conformes aux standards de dette senior LBO.

## 1. Objet de la Mission
Est-ce qu'un rachat de Renault SAS par LBO est faisable financièrement ?

## 2. Indicateurs Clés Certifiés (Norme Python IEEE-754)
| Poste Comptable Officiel | Métrique Calculée | Valeur Certifiée | Unité | Interprétation | Ancrage |
| :--- | :--- | :--- | :--- | :--- | :---: |
| **CAPACITE D'AUTOFINANCEMENT** | `compute_pct_change` | **+83.02** | PERCENT | HAUSSE | ✅ Source |
| **DETTES FINANCIERES (Note E.4)** | `compute_pct_change` | **-37.55** | PERCENT | BAISSE | ✅ Source |
| **DETTES FINANCIERES (Note E.4)** | `compute_ratio` | **1.8541** | RATIO | RATIO_POSITIF | ✅ Source |
| **DETTES FINANCIERES (Note E.4)** | `compute_ratio` | **0.552** | RATIO | RATIO_POSITIF | ✅ Source |

## 3. Analyse & Diagnostic Financier
Synthèse financière exécutive établie par le Crew (Chief Financial Officer (Executive Writer)) sur la base exclusive des calculs certifiés par le moteur Python IEEE-754 :

- **CAPACITE D'AUTOFINANCEMENT** (`compute_pct_change`) : **+83.02%** (HAUSSE) [Base certifiée : n1=1,378.00, n=2,522.00].
- **DETTES FINANCIERES (Note E.4)** (`compute_pct_change`) : **-37.55%** (BAISSE) [Base certifiée : n1=7,487.00, n=4,676.00].
- **DETTES FINANCIERES (Note E.4)** (`compute_ratio`) : **1.8541 RATIO** (RATIO_POSITIF) [Base certifiée : numerateur=4,676.00, denominateur=2,522.00].
- **DETTES FINANCIERES (Note E.4)** (`compute_ratio`) : **0.552 RATIO** (RATIO_POSITIF) [Base certifiée : numerateur=4,676.00, denominateur=8,471.00].

Diagnostic ComEx : L'ensemble des indicateurs ci-dessus sont physiquement ancrés dans les états financiers et validés sans écart par le juge contradictoire GenRM.

---
### 🛡️ Fiche de Traçabilité & Conformité Native MoM
- **Modèle Spécialiste** : `NVIDIA-Nemotron-3.5-Bolt-E4B`
- **Adaptateur LoRA Financier** : `BtbAgent/nemotron-3.5-Bolt-E4B-specialiste-financier`
- **Juge GenRM** : `CERTIFIED` (Certification GenRM (Hybride Neuro-Symbolique) : 100% conforme.)
- **Empreinte SHA-256 Source** : `5f8186cfdca20e931a74f002d1508cd239d19b03ca1fc5560976c035acaa1ce7`
```

*(Note on the 2024 figures: On Renault SAS's standalone 2024 accounts, CAF rose +83.02% from €1,378M to €2,522M while Financial Debt fell -37.55% from €7,487M to €4,676M, bringing Debt/CAF down to 1.85x and Gearing to 55.2% — clearing `LboCovenantPolicyVO` thresholds as `FAVORABLE`, whereas earlier 2023 standalone debt levels of €10,587M vs. €1,245M CAF yielded 8.50x and `UNFAVORABLE`.)*

Deploying LLMs in regulated, mission-critical environments (finance, medical, legal) requires abandoning several common AI tropes:

| Legacy Chatbot Trope | Enterprise Production Architecture | 
|---|---|
| Giant single prompts trying to do everything | **Multi-Agent DAG Orchestration** (Generalist Planner $\leftrightarrow$ Specialist Advisor). | 
| Letting models do arithmetic in latent tokens | **Deterministic Execution Sandboxes** (Python Decimal IEEE-754 runtime). | 
| Relying on LLM self-correction | **Adversarial Gatekeeping** (GenRM Critic with strict AST & polarity checks). | 
| Naive similarity search across raw text chunks | **Neuro-symbolic Chapter Locators** (TOC discovery + Concept Fingerprinting). | 
| Unverifiable qualitative summaries | **Mandatory Same-Row Grounding `[p.X]`** across 100% of claims. | 

Over the first three parts of this series, we built:

In the final installment of this series, we put our complete sovereign pipeline through the ultimate real-world battle test:

Stay tuned for Part 4!

*Thank you for following along with this series! If you found these architectural patterns useful, leave a reaction, share it with your engineering team, and join the discussion in the comments below: What safeguards do you rely on when deploying LLMs to high-stakes workflows?*
