Context
The pipeline takes a universe of US-listed equities, runs a deterministic fundamental screen over them — solvency, dilution, revenue growth, ROIC, valuation — and produces a ranked output with a confidence band that a person can read once a week and act on.
The public slice of that output is the free insider cluster-buy watchlist on edenfintech.com. The pipeline underneath is considerably broader than what gets published.
The problem
The scoring maths is the easy part. The hard part is that a language model asked to review an investment thesis will agree with whatever number you show it.
Show a reviewer the score, the probability and the price target, and it will write a narrative that ratifies them. Show it last week’s estimate and it will anchor to that. Ask it in the prompt not to do this and it will comply for a few runs and then drift, and you will not notice, because a fluent second opinion looks exactly like a good one.
There is a related, more boring failure: once an LLM SDK is imported into the core of a pipeline, it stops being possible to say with confidence which parts of the output are deterministic.
What I did
The core pipeline is stdlib-only Python. LLM calls sit behind an adapter exposing a Callable[[dict], dict] transport, so the core runs with no provider SDK imported at all — the requirements.txt is a one-line comment saying so. Swapping a provider is a transport, not a refactor.
The agent graph is three roles with different jobs. An analyst produces a narrative read over each candidate. A validator checks specific claims against the data. An epistemic reviewer writes a second opinion — but the data it receives is EpistemicReviewInput, a frozen dataclass that excludes scores, probabilities, valuations and numeric targets. That is not an instruction the model can drift away from. It is a type-level guarantee that the reviewer cannot see the scorecard it is meant to be independent of.
Around that sits the machinery for catching the failures anyway: a probability-anchoring detector that flags drift toward round numbers or the prior week’s figures, a bias-check stage, and a three-agent unanimous exception panel that only triggers when the deterministic screen and the scored view disagree. Every LLM call is logged with content-hash dedup, so any run can be reproduced after the fact.
The screening checks themselves are contract-governed and regression-tested against fixtures, and assets/methodology/strategy-rules.md is the single source of truth. When a helper function disagrees with the methodology document, the methodology wins and the helper is the bug.
The result
A pipeline that has been used in anger rather than demoed: 22 numbered batch run directories, nine sector-knowledge files hydrated across runs, and CI mirroring the full local safety-check set.
The design principle generalises past this project. LLM discipline that lives in a prompt is a suggestion. LLM discipline that lives in a frozen dataclass is a constraint, and it still holds on the run nobody is watching.


