Six specialised agents research a watchlist, four of them in parallel, and hand a seventh artefact — a scored, sourced, decision-ready workbook — to whoever has to act on it. The interesting engineering is not the research. It is the contract that stops six independent researchers from disagreeing with each other.
One agent defines the rules. Four go and find things. One decides.
framework.json is the single source of truth. No agent negotiates.
Agent 1 does no research. Its whole job is to write down what the other five will be held to: which parameters exist, the 0–10 rubric for each one — specific enough that two researchers score the same raw value identically — the category weights, the decision thresholds, the four red-flag definitions and which parameter each derives from, and the CSP field list copied verbatim so agent 5 has nothing to interpret.
The payoff is that weights and thresholds are configuration, not code. Changing Quality & Moat from 40% to 30% is an edit to one JSON file; no agent logic moves, and the next run reprices every name consistently. It also makes the mandatory parameters enforceable: F4 and F5 must exist because RF2 and RF1 derive from them, and Q2 must exist because RF3 does. Agent 1 validates that before it finishes, along with the weights summing to 1.0.
| Category | Weight | Carries |
|---|---|---|
| Quality & Moat | 40% | Moat durability, 5-year ROIC consistency, capital allocation, margin of safety. The largest weight, deliberately — it is the part that is hardest to fake and slowest to change. |
| Fundamental | 25% | Valuation against sector, revenue and EPS growth, debt-to-equity, free cash flow trend. |
| Technical | 20% | Moving averages, RSI, MACD, Bollinger position, 1- and 3-month momentum. |
| Sentiment | 15% | Analyst consensus and revisions, 30-day news, insider activity, institutional ownership change. The smallest weight — it is the noisiest input. |
Some findings must not be averaged away by a good score somewhere else.
A weighted average is a machine for hiding a single fatal fact under nineteen ordinary ones. The pipeline borrows Munger's inversion — avoid stupidity before seeking brilliance — and gives four findings the power to overrule the arithmetic that produced the score.
| Flag | Triggers on | Derived from |
|---|---|---|
| RF1 | Negative or deteriorating free cash flow | F5 — FCF trend, 3+ years |
| RF2 | Debt-to-equity above threshold (default 2.0, sector-adjustable) | F4 — debt-to-equity |
| RF3 | Declining multi-year ROIC trend | Q2 — 5-year ROIC series |
| RF4 | Governance and accounting concerns — restatements, auditor changes, SEC actions, aggressive revenue recognition, heavy dilution, related-party structure | Direct research, informed by Q5 |
A flag whose source parameter could not be assessed is reported triggered: false
with status: "uncertain" — never as a clean pass. An uncertain flag does not trigger
the override, and it does not get to masquerade as a green light either. It is surfaced and left
unresolved, which is the honest state.
RF1 must agree with that ticker's F5, RF2 with F4, RF3 with Q2. A flag that contradicts the parameter it derives from is treated as a merge bug to be fixed before the workbook is written, not shipped with a note. An override with no named trigger is a defect by definition.
If any single category has two or more unresolved parameters, the decision becomes NO ACTION regardless of what the arithmetic produced — and regardless of the override, since the problem is absent evidence rather than business quality.
The restructuring that mattered most.
"Is this a good business to own?" and "is this a good stock to sell puts on?" are different questions with different answers. A high-quality compounder trading at a premium with placid options is a fine business and a poor put. A mediocre business with rich implied volatility is a tempting put and a bad thing to own. Averaging them into one number destroys both signals.
So the cash-secured-put verdict is computed by its own agent and carried to the workbook untouched — adjacent to the decision, never blended into it. It is allowed to read the red flags, because the downside of selling a put is being made to own the stock, and a rich premium on a business with a triggered flag skews toward Skip rather than Good. The influence runs one way only.
Each one answered the question the last one raised.
Five agents, three categories, one spreadsheet. It worked. Then I looked closer.
Moat gets the biggest vote. Options get their own lane. Red flags get a veto, but only downward. Every number brings its receipt.
Every weekday at the close, it does its homework and posts the results. Nobody presses a button.
What you just read is the third cut, not the first. The first was five agents, and it worked: three categories, a threshold, a workbook. It just couldn't tell a durable business from a cheap one, or a business worth owning from a stock worth selling puts on. The second cut fixed that with the three changes below. Each one removes an average that was hiding something.
Scroll to compare →
Then: three scored categories
Now: four — Quality & Moat at 40%
The first framework scored Technical, Fundamental and Sentiment. Fundamental meant valuation against sector, revenue and earnings growth, debt-to-equity and free-cash-flow trend — recent-period arithmetic, all of it. Nothing in the parameter set asked whether the business could defend its returns.
Why it changedThose figures describe a company's last few
quarters, not its ability to keep them. Scored alone, they rank a cheap, fast-growing
business above a durable one, and the ranking flips as soon as a quarter turns. Moat
durability, five-year ROIC consistency, capital allocation and governance were added as a
Q-series and given the largest weight — 40%, more than any other category — so the output is
most sensitive to the evidence that moves slowest and is hardest to fake. The weight is a
value in framework.json, not a constant in code, so the split cost one edit to
the contract. It also made the override possible: RF3 reads a five-year ROIC trend and RF4
leans on a governance baseline, and neither of those parameters existed before.
Then: no options lane
Now: agent 5 — parallel, unscored
The first build answered one question: is this a good business to own? Adding options analysis invited the cheap move — score implied volatility and liquidity as a few more parameters and let the weighted average absorb them.
Why it changedThat would have answered neither question. A high-quality compounder with placid options is a fine business and a poor put; a mediocre business with rich implied volatility is a tempting put and a bad thing to own. A single number cannot carry both, and blending them destroys the two signals it is made from. So the put verdict is computed by its own agent, in parallel with the researchers, and reaches the workbook as Good / Neutral / Skip — adjacent to the decision, never inside it. Exactly one edge crosses between the lanes, and it runs one way: a triggered red flag is written into the CSP rationale, because being assigned a flagged business at the strike is the downside actually being priced.
Then: agent 5, waiting on three
Now: agent 6, waiting on four
The decision agent is the only step in the pipeline that is not parallel. It cannot start until every researcher has written, because its job is to weight, gate and threshold a complete set of files. Adding CSP to the parallel band put a fourth writer ahead of it and pushed it down a slot.
Why it changedThe renumbering is not the change — it is the evidence that the change was cheap. The merge step reads finished JSON and the contract, and nothing else: its thresholds and weights were already values it looked up rather than constants it carried. So a new category and a whole new lane arrived without the merge logic being rewritten to accommodate either. Position six is a consequence of the dependency graph, not a decision about ordering — and the fact that it could move by renaming a file is the property worth protecting as the pipeline grows.
Same scoring. New address.
The second cut was clever and invisible: a workbook on one laptop, only as fresh as the last time I opened it. Now, every weekday after the market closes, the pipeline reruns and checks its own math. The workbook has to pass a formula check with zero errors before anything is published. Then the run goes to a Supabase database, and the dashboard reads the newest one the moment you open it.
The page's key is read-only. Writing goes through one database function that checks a secret token first and turns away any run with no tickers in it. The token lives on my machine, not in this site.
If the database takes more than eight seconds, or sends back a run with missing scores, the page quietly shows a saved copy instead. A half-baked run never makes it to the screen.
A new run doesn't touch a single website file. The code changed once, to read from the database. The data changes every weekday.
Most of the design effort went here, not into the research.
A value that cannot be found or verified is recorded as null with a reason. No agent is permitted to fabricate a plausible number, and a category with too many nulls escalates to NO ACTION rather than producing a confident average of the parts that happened to resolve.
Not a bibliography per stock — a URL and an as-of date on each individual reading, so any single number can be traced to the page it came from and aged appropriately.
IV Rank is defined against a trailing 52-week range, so it is meaningless for a stock listed less than 252 trading days ago. If a data provider publishes one anyway, the agent records that it rejected the figure and substitutes absolute IV, peer-relative IV, and IV against 30-day realised volatility instead.
Implied volatility and greeks are the easiest thing in this pipeline to hallucinate plausibly, so agent 5 may only report retrieved figures with a source and timestamp. Premium yields are labelled indicative rather than executable, and a single premium is never annualised and presented as an expected return.
Scores, weighted totals and decisions are written as live Excel formulas rather than baked values, then validated with a recalculation pass that must return zero errors — plus a manual spot-check of a few rows, because a recalculation check will happily confirm a formula that points at the wrong column.
Each run gets a dated workbook and its own row in the database, one per day. Each run also reads the last one to carry forward the previous verdict. When the system changes its mind, you see the change, not a brand-new opinion.