Skip to content
This repository was archived by the owner on Oct 16, 2026. It is now read-only.
NerionSoftPublic archive

About

No description or website provided.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

14 Commits

Folders and files

Repository files navigation

Remedia — Regulatory contract remediation copilot

When a new regulation enters into force, a large group must re-audit thousands of already-signed supplier contracts, generate amendments, write to every counterparty and track thousands of negotiations. Months of work, dozens of lawyers.

Remedia industrializes the execution volume that surrounds legal judgment — without ever replacing that judgment: the agent maps, groups, generates, tracks and triages; the human expert approves every cohort and decides every escalated case.

Demo built on the DORA case (Regulation (EU) 2022/2554, digital operational resilience for the financial sector): 2,800 supplier contracts of a European bank, 4 mandatory contractual clauses, 9 remediation cohorts.


The cardinal principle: control beats autonomy

These rules are not prompt intentions — they are hard-coded and tested:

Invariant Where it is enforced Test
The agent never triggers an outreach without explicit human approval (approvedBy null ⇒ nothing leaves) Cohort.launch() (domain) + suspended workflow tests/unit/usecases/launch-cohort.usecase.test.ts
Every jurisdictional nuance is systematically escalated: contract law ≠ playbook reference law and contested wording ⇒ forced red, whatever the LLM said ClassifyNegotiationResponseUseCase (application code called by the workflow — not a prompt) tests/unit/usecases/classify-negotiation-response.usecase.test.ts
Cohort clustering is deterministic and 100% explainable (exact group-by, no embeddings) BuildCohortsUseCase tests/unit/usecases/build-cohorts.usecase.test.ts
Red cases have no automatic-resolution path, not even a hidden one Negotiation.resolve() rejects the agent actor same as invariant 2
Every action (agent or human) produces a timestamped, immutable, justified audit event AuditEvent table is append-only: the repository exposes neither update nor delete —

Human approval relies on Mastra's official human-in-the-loop mechanism: workflows suspend (suspend()) at the approval gate and only resume (resume()) on the expert's decision — the suspended state is persisted, a workflow can wait indefinitely.

Architecture

NerionSoft starter (hexagonal architecture) + Mastra for the agentic layer.

flowchart TB
  subgraph UI["Next.js UI (App Router)"]
    P["Portfolio<br/>2,800 virtualized tiles"]
    K["Cohorts<br/>redline diff + approval gate"]
    W["War room<br/>green / amber / red"]
    J["Audit trail"]
    D["/director (demo control room)"]
  end

  subgraph API["API routes — /api/remediation/v1/* (thin, apiHandler)"]
    R1["campaign/scan · build-cohorts"]
    R2["cohorts/:id/approve · request-changes"]
    R3["negotiations/:id/resolve · simulator"]
  end

  subgraph HEX["remediation-hexagone (domain + use cases + ports)"]
    UC1["ScanContractUseCase"]
    UC2["BuildCohortsUseCase<br/>(deterministic group-by)"]
    UC3["Approve / RequestChanges / Launch"]
    UC4["ClassifyNegotiationResponse<br/>+ HARD JURISDICTIONAL RULE"]
    AUD["AuditEvent (append-only)"]
  end

  subgraph MASTRA["Mastra (src/mastra)"]
    WF1["workflow portfolio-scan"]
    WF2["workflow cohort-build"]
    WF3["workflow cohort-remediation<br/>⏸ await-cohort-approval"]
    WF4["workflow negotiation-triage<br/>⏸ await-expert-resolution"]
    A1["agent contract-analyzer"]
    A2["agent redline-generator"]
    A3["agent response-classifier"]
    SIM["counterparty-simulator<br/>(pre-written replies)"]
  end

  subgraph DATA["Local storage (SQLite)"]
    DB[("remedia.db — Prisma<br/>contracts · cohorts · negotiations · audit")]
    MDB[("mastra.db — LibSQL<br/>suspended workflow state")]
  end

  UI --> API --> HEX
  API --> MASTRA
  MASTRA --> HEX
  A1 & A2 & A3 -.->|"DeepSeek (Mastra model router)"| LLM(("LLM"))
  HEX --> DB
  MASTRA --> MDB
  SIM --> WF4
Loading

Campaign lifecycle: scan (2,800 contracts) → build-cohorts (group-by: gaps × law × type) → per cohort: amendment + letter generation → ⏸ expert approval (refusal loop with comments/regeneration possible) → launch (negotiations created) → simulated replies → classification (+ hard rule) → green/amber classified, red ⏸ expert decision → cohort close-out.

Three agents (open-ended reasoning tasks), four workflows (deterministic process). Each suspension is an independent Mastra run, resumable by id (stored on the cohort/negotiation).

Getting started

Prerequisites: Node ≥ 24 and pnpm 11 (corepack enable gives you the pinned version). Everything runs locally on SQLite — the only external dependency is the LLM API.

pnpm install                 # deps + prisma generate (postinstall)
cp .env.example .env         # then set DEEPSEEK_API_KEY (see table below)
pnpm db:migrate              # creates prisma/remedia.db (local SQLite)
pnpm fetch:cuad              # downloads the ~50 real CUAD contracts (Hugging Face)
pnpm dev                     # http://localhost:3000

On first launch the app opens on the workspace chooser (Live / Demo — see below); picking a mode seeds the database accordingly, so there is no separate seeding step. Notes:

  • pnpm fetch:cuad is required for the 50 real contracts (demo portfolio and the live-mode “Import CUAD” starter). If skipped, seeding still works but the portfolio is synthetic-only (a warning is logged).
  • pnpm db:seed remains available to seed the demo dataset from the CLI without going through the chooser.
  • Without an LLM key the app boots and demo mode degrades gracefully; live mode requires the key.
Variable Required Description
DEEPSEEK_API_KEY Recommended LLM key for the 3 agents (the key must match the provider of REMEDIA_MODEL). Without a key the demo remains 100% functional: live analysis of the 50 real CUAD contracts is unavailable (audited as a graceful failure), amendments are composed from the playbook, classification falls back to the pre-annotated templates.
REMEDIA_MODEL No Model (Mastra provider/model format), default deepseek/deepseek-chat. Note: the DeepSeek API currently serves deepseek-v4-flash / deepseek-v4-pro only. openai/… also works (with OPENAI_API_KEY).
DATABASE_URL No Defaults to file:./prisma/remedia.db.
MASTRA_DB_URL No Defaults to file:./prisma/mastra.db.

Scripts: pnpm test (Vitest invariants) · pnpm test:e2e (Playwright) · pnpm lint · pnpm format.

Live mode vs Demo mode

On first launch the app opens on a workspace chooser (the mode is stored in the database; switch later from the navigation bar — switching wipes the data):

Live mode Demo mode
Portfolio Entered manually with full text — optional starter: import the 50 real CUAD contracts Seeded: 2,800 contracts (50 real CUAD + synthetics with pre-computed results)
Playbook Starts blank — optional starter: pre-fill from the reference templates (loaded as drafts; validation stays a human act) Pre-filled from the reference templates (still draft: validation stays a human act)
Analyses / amendments / classifications All live on the LLM Live for real contracts; pre-computed for synthetics
Counterparty replies Typed in by you from the war room ("Record the reply") — same triage pipeline Reply simulator (pre-written templates)
/director Playbook, scan and reset controls only Full control room incl. replayable scenarios

Live mode requires an LLM key (DEEPSEEK_API_KEY).

Demo mode (video shooting)

  1. Open /director (route not linked in the navigation): the control room.
  2. Full reset if needed (2,800 unscanned contracts).
  3. Act 0 — Playbook Studio: validate each clause then the playbook (or use the control-room shortcut “Validate the playbook (as Claire)”). Until then the scan is locked — the human interprets the law once, the agent applies it 2,800 times.
  4. Act 1 — Portfolio: “Run the DORA scan” (batch pace adjustable in the control room). Tiles pop as their state changes; counters, stat tiles and the ROI banner follow live. Wait for the 50 real contracts to finish (~2 min) so they join the cohorts.
  5. Act 2 — “Build the cohorts”: animated condensation of the wall into 9 cohort cards (~3.5 s). Hidden replay: Shift+R on the Portfolio page.
  6. Act 3 — Cohort page: grouping rationale, amendment diff, letter; “Approve” (explicit confirmation) or “Request changes” (the agent regenerates and re-suspends).
  7. Act 4 — War room: start the simulated replies (adjustable pace). Cards arrive and triage themselves; the red ones drop into the “Escalated to the expert” queue (~7-10% of replies — that ratio IS the argument). Resolve a jurisdictional case on camera.
  8. Audit trail: every decision seen in acts 3-4 is there, with its rationale.

Trust Gate & learning loop

Trust Gate — measured trust, not declared. Like a financial auditor, Claire does not re-read 2,800 contracts: before any cohort can move towards approval, the workflow draws a seeded random sample (5% of the cohort, minimum 10 — the mulberry32 seed is stored on the TrustSample, so every draw is reproducible) and suspends. She reviews each sampled contract side-by-side (the excerpt the agent based its conclusion on vs the conclusion itself) and renders a verdict: confirm or correct (correction comment mandatory). When the last verdict lands, the workflow resumes and computes the agreement rate:

  • ≥ 95% → the sample is passed; the cohort proceeds to generation and approval, and carries a permanent badge (“Verified by sampling — 96% agreement, 14 contracts, iteration 1”).
  • < 95% → the corrections are injected and the cohort is re-analyzed, a fresh sample is drawn, the workflow re-suspends. The iteration comparison (“iteration 1: 67% → iteration 2: 100%”) makes the learning loop visible.
  • 3 failed iterations → the cohort exits the automated flow (manual_review_required). The tool owns its limits.

The invariant is hard-coded, not cosmetic: GenerateCohortRemediationUseCase and ApproveCohortUseCase (and the approve route) all refuse to act without a passed sample for the latest iteration — tested in tests/unit/usecases/trust-gate.usecase.test.ts.

Learning loop — even the learning is deterministic and audited. Every corrected verdict becomes an ExpertCorrection row. On re-analysis, the relevant corrections are selected by an explicit SQL query (same campaign, the cohort's clause codes, 10 most recent) — deliberately not Mastra's semantic memory, which recalls by similarity and cannot be audited. They travel through Mastra's RequestContext to the contract-analyzer agent, whose instructions are a function: when corrections are present, a few-shot block (“The expert reviewer has corrected the following conclusions…”) is appended to the base prompt. Every analysis that ran with injected corrections produces an audit event listing the exact correction ids — the question “which human correction influenced which analysis?” always has a precise answer. There is no fine-tuning and no training call: the “learning” is few-shot injection, presented as such.

Demo scenario (seeded): one cohort (Data provider · IT law) over-detects audit-rights gaps — the agent confused the confidentiality clause with the audit clause on a third of the contracts. The first sample fails (~65%), Claire corrects 3-4 conclusions, the re-analysis removes the false positives from the cohort, the second sample passes. The /director console has a shortcut straight to that review.

The lawyer authors, the agent assembles

New legal wording enters Remedia through exactly two doors, and both are a keyboard in front of a lawyer:

  1. Playbook Studio (/playbook) — the first step of any campaign. Claire writes (or loads the starting template and adapts) the preferred wording, the ordered fallback wordings and the hard limits for each required clause, then validates clause by clause, then validates the playbook. The invariant is structural: the portfolio scan cannot start while the playbook is not validated — enforced in the workflow's first step and the scan route (HTTP 409), and materialized in the UI (“Scan locked — playbook not validated”). Any later edit sends the clause back to draft, revokes the campaign validation, closes the scan gate again, and lists the already-built cohorts that reference the clause. Every save carries before/after snapshots in the audit payload: the append-only journal is the version history.
  2. Expert Resolution (war room → escalated case) — a dedicated three-panel page (counterparty response / current amendment / playbook positions with hard limits in sight) offering exactly three actions, never an automatic one: apply a fallback (the amendment is regenerated strictly constrained to that human-validated wording, vN → vN+1 diff, Claire confirms), hold the position (deterministic letter template, no LLM drafting, Claire reviews then sends), or author a new wording — an editor that is never prefilled and has no LLM call wired to it. The agent may only adapt the FORM of what she typed (party names, numbering), with the form-only diff displayed.

The elegant bit: in case 3, one checkbox promotes the authored wording to the playbook as a fallback (new version, audit event naming the author). The response-classifier reads the fallbacks live at classification time, so the next similar counter-proposal classifies amber instead of red. The playbook enriches mid-campaign, by the expert, fully audited — learning without ML.

Verification layer

Guiding principle, in order: verify deterministically wherever possible; use a second LLM only where the risk is asymmetric; automatic verification never replaces a human gate — it only decides what gets escalated to the human.

  1. Slot-diff — “form, never substance” made physical. Playbook wordings are templates with typed slots ({{party_supplier}}, {{party_customer}}, {{section_ref}}, {{defined_term:notice_recipient}}, {{effective_date}}). After every generation, a deterministic verifier (verifySlotDiff, pure function, zero LLM — src/remediation-hexagone/domain/services/slot-template.ts) checks that every difference between the approved template and the generated text sits inside the slots; slot values are themselves validated against the contract metadata. Any out-of-slot deviation → the cohort is generation_blocked: the amendment is stored for the violation display (expected vs found, highlighted) but the state machine makes approval and launch unreachable. The expert regenerates (re-verified) or takes the case manual. At launch, each of the N per-contract instantiations is a deterministic slot fill, batch-re-verified (duration audited).
  2. Grounding check — every quote verified in the source. verifyGrounding (pure, deterministic — exact match, then normalization-tolerant match on whitespace/case/punctuation, nothing semantic) checks each cited evidence against the contract text and stores the matched offset. An unfound quote is not deleted (that would be autonomy): the detection is flagged unverified and force-included in its cohort's Trust Gate sample, so a human rules on it before anything moves. The ROI banner shows “Quotes verified in source: X/Y”.
  3. Green verifier — the second look where the risk is asymmetric. A mis-classified red or amber still lands on a human; a mis-classified green heads to quasi-automatic acceptance — the only path without human eyes. So greens (and only greens — running it on amber/red would cost money and dilute the asymmetry argument) get a second, deliberately adversarial LLM review (green-verifier agent, its own versioned prompt). The fusion rule is hard-coded in application code, not in a prompt: a disagreement downgrades green → amber (human validation) with the deviations appended to the rationale; the verifier can never upgrade and never resolve. A technical failure fail-safes to amber, never green.

Every check — pass or fail — lands in the audit trail with its own filterable event type (“Verification only” filter), and three ROI counters keep the layer visible: quotes verified, amendments blocked, greens downgraded. /director has one replayable scenario per mechanism.

Real / simulated boundary (owned honestly)

Real Simulated
The engine: agents, suspend/resume workflows, jurisdictional rule, audit trail — everything actually runs. The scale: 2,750 synthetic contracts (fictional counterparties, pre-computed analysis results applied at scan time) for the mass effect.
50 real commercial contracts (CUAD corpus, SEC/EDGAR base), analyzed live by the LLM during the scan — their DORA gaps are authentic (contracts predate the regulation). The banking metadata of those 50 contracts (governing law, criticality, value, renewal dates) — the text is real, the context is fictional. The displayed law comes from the metadata; the law detected in the text by the agent is logged for reference.
The DORA playbook (4 clauses, preferred wording + fallbacks + hard limits) written for the demo in credible legal English. The counterparty replies: ~40 pre-written variants injected by the simulator (no real email, no LLM calls in the reply loop).
Parameterized ROI baseline: 45 min/contract manual, €150/h (constants documented in get-campaign-metrics.usecase.ts).

Corpus: CUAD v1, The Atticus Project, CC BY 4.0 (credited in the app footer) — fetched via the Hugging Face dataset dvgodoy/CUAD_v1_Contract_Understanding_PDF (pnpm fetch:cuad). Original paper: CUAD: An Expert-Annotated NLP Dataset for Legal Contract Review (Hendrycks et al., 2021).

Quality

  • Strict TypeScript, zero any; ESLint + Prettier.
  • Every LLM output validated with Zod, 1 retry with the parsing error fed back to the model, then graceful fallback + audit event (analysis_failed, redline_generation_fallback, classification_fallback).
  • Tests focused on the critical invariants only (11 tests) — no coverage race.
  • Typed domain errors (DomainError), mapped to HTTP through a registry (NerionSoft starter).

Known limitations

  • A single mocked user (“Claire”) — no multi-user auth (out of scope).
  • The control-room reset re-initializes the business database; suspended Mastra runs from previous takes become orphans in mastra.db (harmless; delete the file to start completely fresh).
  • Outreach (letters) is simulated — no external integration.
  • Regulatory watch (detection) is out of scope: the starting point is the expert loading the regulation and the playbook.
  • Without an LLM key (DEEPSEEK_API_KEY by default), the 50 CUAD contracts stay unscanned (audited failure) — the rest of the journey is unchanged.

Structure

src/
├── mastra/                       # Agentic layer (agents, workflows, versioned prompts, simulator)
├── remediation-hexagone/         # Business domain (entities, use cases, ports, prisma/mastra/in-memory/seed adapters)
├── presentation/                 # React UI (portfolio/cohorts/negotiations/audit/director features)
├── infrastructure/               # Prisma+SQLite, Pino logger, apiHandler, mocked auth
└── app/                          # Next.js App Router (pages + API routes)
data/contracts/                   # 50 CUAD contracts (pnpm fetch:cuad) — gitignored
scripts/                          # fetch-cuad.ts, seed.ts
docs/                             # NerionSoft starter architecture & conventions

Hackathon demo project — NerionSoft platform-starter-nextjs starter, Mastra, Next.js 16, Prisma 7/SQLite, Framer Motion.

About

No description or website provided.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages