Inspiration

We met at the University of Maryland's Smith School and kept ending up on opposite sides of the same spreadsheet.

Mitt has spent his career inside the books. As an accounting intern he reconciled 1,500+ transactions against bank statements by hand. As a real estate tax associate he tied trial balances to client records. Today he prepares Schedule K-1s for private equity, hedge fund and fund-of-funds clients, where one wrong partner allocation flows into every investor's return. He also built FlowSync, a Python automation platform that cut a multi-location business's back-office processing by more than 60%. His conclusion: most of the close is still copy, paste, tick and tie.

Mit has spent his career on the other side of those numbers. As an investment banking analyst, he builds the models, comps and diligence that capital decisions rest on. Before that, he managed escrow and reserve processes for 10,000+ accounts across 5,000 multifamily borrowers in a loan servicing seat, and presented portfolio and sensitivity reports to credit lenders. His conclusion: decision-makers almost never see the books themselves. They see a summary of a summary, months late.

Then 2026 happened to private credit. Fitch's U.S. private credit default rate hit a record 6.0% in Q2. Large funds capped redemptions as requests ran into the high teens. And a large share of new lending went into AI infrastructure: data centers, GPU-backed loans to neoclouds, leases to hyperscalers. A pension invested in one of those funds learns that a borrower switched to paying interest in kind, or that a tenant was downgraded, through a quarterly statement that arrives a quarter late.

We realized the most predictive data about an investor's risk already exists, buried in the monthly books. Nobody connects it to the market. That's RAMA.

What it does

RAMA turns a borrower's monthly books into source-cited risk insights for limited partners (LPs), released only after a human approves them.

  1. Close. Agents close a portfolio company's month from its ledger and bank statements: reconciling cash, explaining every break with the source document, and producing audit-ready workpapers. Anything unsupported stays an explicit exception.
  2. Signals. The verified accounting is combined with dated market evidence from S&P Capital IQ Pro (issuer ratings, outlook actions, leverage, interest coverage) and cited public data such as GPU rental prices.
  3. Rules. Fourteen deterministic rules compute the indicators. Thresholds come only from the LP's own policy, the loan documents, or clearly labeled defaults. Missing inputs show as "not evaluated", never estimated.
  4. Cards. The model words what the rules computed, and nothing more. Every card shows its metric, threshold, source and as-of date. A blocklist and a classifier stop investment advice.
  5. Release. A reviewer approves cards individually or as a package, and only approved cards reach the LP's reporting view.

How we built it

The core design rule: the model never does math. Every number comes from a typed, unit-tested Python function. The agent decides which function to call and explains what came back, with a citation.

The close rests on a few identities that must hold for every account and every investor:

$$ \text{Opening balance} + \text{Activity} = \text{Closing balance} $$

$$ \text{Ledger cash} - \text{Outstanding items} \pm \text{Unrecorded items} = \text{Bank balance} $$

The intelligence layer is a set of fixed rules. Two examples:

$$ \text{Income quality (R1)} = \frac{\text{PIK principal} + \text{Non-accrual principal}}{\text{Fund NAV}} $$

$$ \text{LTV}{\text{now}} = \frac{\text{Drawn principal}}{\text{Appraised collateral} \times \dfrac{\text{GPU rental index}{\text{now}}}{\text{GPU rental index}_{\text{appraisal}}}} $$

A card fires when a computed value crosses a threshold the LP set. For example, borrower leverage fires when \( \frac{\text{Net debt}}{\text{EBITDA}} > 6.0\times \). For our hero borrower's public proxy, Capital IQ shows 8.2×.

How we used Nicrron and PRISM

We treated Nicrron and PRISM as two halves of one discipline: every model call goes through one door, and every run leaves a record we can inspect.

Nicrron: one route for every model call.

  • RAMA's agents call Claude (anthropic/claude-sonnet-5) exclusively through Nicrron's OpenAI-compatible API, using standard tool calling. The close agent, the card writer and the guardrail classifier all share that route.
  • We built a dedicated NicrronProvider into our agent runner. In hackathon mode (BOB_HACKATHON=1), every model call goes through Nicrron, with no silent fallback to a direct provider. If Nicrron is unavailable, the run fails visibly instead of quietly switching models.
  • Because the agents use tool calling, the model never computes a number. It picks which of our Python functions to call (reconcile_cash, match_split_payments, lookup_source_row, the rule functions), and Nicrron passes those calls and their results back and forth.
  • API keys live only in the environment, and our log redaction strips Nicrron keys from anything we write out.

PRISM: seeing what the agent actually did.

  • Every run is traced. Our bob/trace.py module sends each run to PRISM as a session: every tool call as a step, the agent's trajectory, the model calls and the guardrail decisions. A single close produces a session of 9+ tool calls you can step through. A verify command checks the PRISM credential and live status, and we only report a trace as "received" when PRISM returns a 2xx with an id.
  • We designed the agent so PRISM could judge it. PRISM evaluates runs by comparing what the agent intended with what it did. So the system prompt states the intent explicitly ("the close passes only when every difference is matched or explained with a source row"), and every run ends with a structured summary of each check, its difference, status and source. A failure shows up in the agent's own behavior, not just in a stack trace.
  • Alerts on real failures. Our final tool, finalize_close, raises an error if unexplained differences remain above tolerance. A PRISM alert on failed tool calls fires whenever the agent tries to close books that don't tie.
  • Root cause, then fix. We ran the close agent across 72 synthetic cases whose expected answers sit outside the agent's reach, and let PRISM cluster the failures. The cluster pointed to one cause: v1 matched bank lines to the ledger one-to-one by exact amount, so a payment that settled in two pieces looked like missing cash. We added a match_split_payments tool and a prompt rule to try it before declaring a break (v2), then reran the same 72 inputs. {{REPLACE: e.g. "Failures in that cluster dropped from X to 0, and the alert stopped firing."}}
PRISM proof What it showed in RAMA
Sessions One close run as a traced session of 9+ tool calls
Alert Fired on failed finalize_close calls when the books didn't tie
Root Cause Clustered v1 failures on split settlements; v2 fixed them on the same inputs

Stack:

Layer What we used
Model routing Nicrron (OpenAI-compatible API, tool calling) to Claude, with no fallback route
Observability PRISM sessions, alerts and root-cause clustering on every run
Calculation Deterministic Python engine with tests: reconciliation, workpapers, rule functions
Evaluation 72 synthetic close cases with hidden expectations; v1 and v2 runs
Market data S&P Capital IQ Pro screen (12 public companies: hyperscaler tenants, neoclouds, data center operators, chipmakers)
Product FastAPI and PostgreSQL backend, Next.js workspace, review workbench and approval flow
Guardrails Prescriptive-language blocklist, a descriptive-vs-prescriptive classifier, a number check against rule outputs

Our demo scenario is fully synthetic: a $1.2B private debt fund lending to AI infrastructure, a pension LP, and a GPU-cloud borrower whose market context comes from CoreWeave's public data. The borrower's September interest doesn't tie to the bank. RAMA finds the payment-in-kind (PIK) election that explains the gap, carries that reconciled number into the fund's income-quality indicator, and traces every card back to the bank line it came from.

Challenges we ran into

  • Keeping the AI honest about numbers. Our early instinct was to let the model "summarize" results. It occasionally rounded, combined or rephrased figures. We moved to a hard rule: every number in a card must match a rule output exactly, or the card is blocked.
  • Making failures visible to PRISM. Our first agent failed quietly: it would describe a break and stop, which looked like a normal run. We had to state the agent's intent explicitly and make finalize_close refuse unexplained differences, so PRISM could tell a real success from a polite failure.
  • Getting enough volume for root cause. Clustering needs many failed runs, so we built 72 synthetic cases across several break types and ran them before touching the code, staying within PRISM's daily analysis budget.
  • The line between insight and advice. "Your exposure crossed your limit" is analytics; "consider reducing your exposure" is advice. Writing a guardrail that catches the second without crippling the first took several rounds of red-teaming our own prompts.
  • Real data has gaps. Our Capital IQ license didn't include market-implied default probabilities, Fitch fields came back empty, and debt and EBITDA were reported on different periods. We redesigned rules around what we could verify, labeled every period, and made "not evaluated" a first-class state instead of guessing.
  • Proxies are not borrowers. Using a public company as context for a private borrower is useful and dangerous. We labeled every proxy on screen and kept proxy data out of the borrower's own accounting.
  • Borrower-to-fund aggregation. A borrower's books can't tell you fund leverage or an LP's liquidity. We had to separate what comes from the close from what needs fund and LP records.
  • Time. One day, two founders, a full close engine, an intelligence layer and a demo.

Accomplishments that we're proud of

  • An end-to-end path from a bank statement line to an approved LP insight, where every step is inspectable.
  • A close that refuses to force a reconciliation to balance.
  • Insight cards that show their own math: metric, threshold, who set it, source and date.
  • A single, enforced model route through Nicrron, with no hidden fallbacks.
  • A real fix found by reading our own PRISM failure clusters and confirmed on the same 72 inputs.

What we learned

  • Trust is an architecture, not a feature. Deterministic code for math, models for language, humans for release. Each layer does one job.
  • The best risk data is boring. Cash interest collected versus accrued, a quiet switch to PIK, shrinking covenant headroom: these show up in the books before they show up in a rating.
  • Observability changes how you build agents. A failure cluster with a cause sentence attached beats any amount of log reading. Designing the agent to state its intent made it far easier to evaluate.
  • One route beats many. Forcing every call through Nicrron made costs, behavior and failures consistent and easy to trace.
  • Saying "we don't know" is a product decision. A visible gap earns more trust than a confident estimate.

What's next for RAMA

  • Pilot with an emerging private credit manager on real (permissioned) borrower reporting.
  • Fund-level close: capital calls, management fees, waterfall, and per-investor capital accounts.
  • Daily market signals (default probabilities, CDS, event feeds) under each customer's own data license.
  • Extend PRISM evaluation from the close to the intelligence layer, measuring rule precision against actual loan outcomes.
  • A real integration into the LP portfolio-monitoring tools that GPs already use.

RAMA provides risk analytics, not investment advice. All fund, borrower and investor data in this project is synthetic; public market data is from S&P Capital IQ Pro and cited public sources.

Built With

Share this project:

Updates

Submission history