Self-reference guard and entity casing fixes across both extraction paths

Two small commits fix a real production bug and a display annoyance in the counterparty and intake extraction paths. If you're pulling either extraction feature, pull both of these with it.

contract-reviewintake

a9426b0c addresses a specific class of extraction failure: the LLM was sometimes returning the user's own organization as the counterparty. On a contract uploaded by a Kodex employee, the model returned "Kodex, Inc." - biased by the filename. The fix has two parts.

First, buildSystemPrompt() now injects the user's organization name from user_profiles.organisation directly into the system prompt: "the user's own organization is X - never return X as the counterparty." Second, an isSelfReference() guard normalizes company suffixes (Inc, LLC, Ltd, Corp, and about a dozen others) and nulls the extracted value if the model still returns a self-match despite the instruction. Both fixes apply symmetrically to counterpartyExtraction.ts and intakeAnalysis.ts.

The guard is forgiving by design: if organisation is unset on the user's profile, it's a no-op rather than an error. The only schema dependency is the organisation column on user_profiles.

282e57c1 adds normaliseEntityCase() to both extraction modules - about 117 lines of token-by-token case normalization. The problem: many contract preambles spell party names in ALL CAPS, so raw extraction returns "ADOBE INC." The function converts fully-uppercase or fully-lowercase strings to title case while preserving conventional suffix forms: "Inc." not "INC", "GmbH" not "GMBH", "plc" not "PLC". Short acronyms four characters or under (IBM, AT&T) are left alone.

So what Pull these if you're pulling the extraction paths. The self-reference guard in particular addresses a bug class any auto-extract feature will encounter - it's not Custos-specific. Requires `user_profiles.organisation` to be populated to have effect; users who haven't set it get no benefit from the guard.

View this fork on GitHub →

Spotted something wrong? Or know the PR text has fresher detail than the writeup above?

Commits in this thread

2 commits from Custos/legalos, oldest first. Source extracted verbatim from the harvested git log.

SHA Subject Author Date
a9426b0c Intake/counterparty: pass user organisation to LLM + self-reference guard Custos 2026-05-04 ↗ GitHub
commit body
The intake and counterparty extraction prompts didn't tell the model
which side of the agreement was "us", so the LLM sometimes returned the
user's own org as the counterparty (Kodex, Inc. on Adobe contracts -
biased by Kodex appearing in the filename).

- userSettings now reads + returns user_profiles.organisation.
- buildSystemPrompt() in intakeAnalysis + counterpartyExtraction injects
  "the user's own organization is X - never return X as the counterparty"
  into the system prompt.
- isSelfReference() guard normalises company suffixes (Inc, LLC, ...) and
  nulls counterparty/parent if the model returns a self-match anyway.
- Orchestrators thread organisation through the analysis call.

Set your org in /account/profile (organisation field) for this to work.
282e57c1 Normalise entity casing on extraction (Adobe Inc., not ADOBE INC.) Custos 2026-05-04 ↗ GitHub

Capture this thread into my fork

Download a single Markdown prompt that tells Claude how to port every commit above into your working tree — adapting paths and structure to match your repo. Run it via claude -p < capture-thread-43.md from inside the repo you want the changes in.

⬇ Download capture-thread-43.md