AlphaSense SuperAnalyst Insights: Building an Assumption Monitor in TypeScript

Build a TypeScript report monitor that rebuilds only when assumptions truly change and asks when data is missing.
AlphaSense's SuperAnalyst keeps AI output working after analysis, monitoring assumptions and auto-updating conclusions. This article builds a TypeScript miniature: the report carries a tolerance per input, and a four-state classifier (Fresh, Noise, Stale, Missing) decides whether a change warrants a rebuild. Fresh does nothing, Noise logs, Stale rebuilds and bumps the version, Missing asks rather than reusing stale values. A vendor renewal memo rebuilds three times in five days — two are correct; daily cron wastes rebuilds, never rebuilding sends wrong advice.
Most AI answers are done the moment they're written.
That's also the moment they start going stale.
The memo says "renew." Two weeks later the price goes up, half the seats are empty, and the memo still says "renew" — with full confidence.
AlphaSense announced SuperAnalyst, a persistent agent for financial and strategic work. It can plan work, verify evidence, run code, and produce memos, models, and dashboards. What really stuck with me is what it does afterward: unlike static AI output, SuperAnalyst keeps working after the initial analysis is done, monitoring the assumptions behind investment theses, portfolios, trades, or strategic decisions, identifying significant shifts in the market, and updating work products with new information — without any new prompt.
That distinction matters. The output isn't the end of the task — it's a promise that only holds while the assumptions hold. An answer that can't tell you what it assumed, or when it's wrong.
This article builds a miniature version in TypeScript to show how a memo can stay stable under noise, rebuild when something truly changes, and speak up when data goes missing.

The Core Problem: Daily Rebuilds and Never Rebuilding Are Both Wrong
There are two easy ways to keep a report current: rebuild every day, or never rebuild.
The first burns compute and changes the answer on meaningless noise; the second is exactly why stale memos get approved. The useful version lives in between: rebuild only when something that actually matters changes, otherwise leave it alone.
"Significant" is the whole feature, and everything else is just a cron job. The hard part of that judgment is separating noise from signal — a 3% swing in seat count and a 10% price increase have completely different impacts on the conclusion.
Step One: Write Down the Assumptions
The report carries the inputs it was built from, along with a tolerance for each one. tolerancePct says how far a value can drift before the conclusion is worth questioning. Tight tolerance for price, loose tolerance for seats — that's a human judgment call.
type Assumption = { key: string; value: number; tolerancePct: number };
type Report = { title: string; assumptions: Assumption[]; build: (v: Record<string, number>) => string; text: string; version: number };
The build function is the analysis itself: same inputs, same output. This guarantees reproducibility on rebuild — if the inputs haven't changed, the output shouldn't either.
What is tolerance? In engineering and statistics, tolerance refers to the range within which a quantity can deviate from a baseline and still be considered "equivalent." This article borrows the concept, giving each assumption a tolerancePct (percentage tolerance) that expresses how much the value can drift while the conclusion still holds. It's essentially a business rule: price is usually sensitive to the conclusion, so the tolerance is small (e.g., 5%); short-term swings in seat count or usage rate are often normal, so the tolerance is large (e.g., 10%–15%).
Why percentages instead of absolute values? Percentage tolerance makes inputs of different magnitudes comparable, and decouples the threshold from the scale of the number. But it has a cost: a small absolute change in a small decimal gets amplified into a high percentage (for example, a 0.02 move in usage rate), while a huge absolute change in a large number may only produce a modest percentage. More mature systems combine absolute thresholds or statistical significance to set boundaries.
What is "rebasing"? When a report rebuilds due to stale, each assumption's baseline value is updated to the current live value, and the next comparison starts from there. This avoids error accumulation: if you always compare against the original baseline, three months of steady small drift will eventually trigger a rebuild all at once, masking the true rhythm of change. Rebasing resets the counter on every rebuild, at the cost of losing the global view of long-term drift — usually covered by keeping separate historical records.
Step Two: Measure the Magnitude of Change
Calculate percentage change from the current value relative to the value at report-build time, not relative to yesterday.
function movePct(old: number, now: number): number {
if (old === 0) return now === 0 ? 0 : Infinity;
return Math.abs((now - old) / old) * 100;
}
This matters. Ten tiny daily drifts can add up to one large change. If you always compare against yesterday, you'll keep rebuilding on noise while missing slow, cumulative, significant shifts.
Step Three: Classify the Change
The check function returns one of four results:
- Fresh: nothing moved.
- Noise: something moved, but within tolerance. Log it, don't act.
- Stale: something moved outside tolerance. Rebuild.
- Missing: the live value is gone. Ask, don't guess.
type Check =
| { kind: "fresh" }
| { kind: "noise"; moves: string[] }
| { kind: "stale"; moves: string[] }
| { kind: "missing"; keys: string[] };
Missing is checked first on purpose. A report that quietly reuses old numbers is worse than a report that says "I don't know right now." The former lets you decide on a false premise; the latter at least exposes the uncertainty honestly.
Step Four: Rebuild Only When Stale
Only stale touches the report.
function refresh(r: Report, live: Record<string, number | undefined>): Check {
const c = check(r, live);
if (c.kind !== "stale") return c;
const values: Record<string, number> = {};
for (const a of r.assumptions) {
a.value = live[a.key]!;
values[a.key] = a.value;
}
r.text = r.build(values);
r.version += 1;
return c;
}
On rebuild, every assumption is rebased to its live value, and the version number is incremented so anyone can see the answer changed. The version number is the most underrated part of this system — it turns "when did the answer change, and why" into a traceable history.
Why do version numbers matter? In any scenario where you need to track "why did the conclusion change," version numbers are the core of the audit trail. They let downstream consumers answer: what's the difference between v1 and v3 of this report? Which assumption drove the change? Without version numbers, users only see "the latest answer" and can't tell when or why it became what it is. The r.version += 1 here looks trivial, but it's the only mechanism that turns a rebuild event into traceable history.
Contrast with immutability. The refresh function here directly mutates fields on the Report object (a.value, r.text, r.version) — in-place updates. In many production systems, the safer approach is to return a new report object and keep the old version as an immutable snapshot, enabling rollback, comparison, and concurrent reads. This article uses in-place updates for teaching simplicity, but in multi-agent collaboration or audit-required scenarios, immutable snapshots are usually the more prudent choice.
How is idempotency guaranteed? Because build is a pure function — same inputs always produce the same output — repeated calls to refresh won't produce a new version number or change the report body as long as the assumptions haven't changed. That idempotency is the foundation of the whole monitor's reliability: it means the act of "rebuilding" introduces no additional noise, and changes only come from real changes in the inputs.
Step Five: Run It
Demo with a fictional memo: whether to renew a vendor contract, with assumptions about seats, price per seat, and usage rate.
tiny-assumption-watch demo
v1: Annual cost $57600. 74 of 120 seats used. Renew as is.
mon [FRESH] nothing moved
tue [NOISE] seats 120 -> 124 (3.3%, tol 10%); usageRate 0.62 -> 0.6 (3.2%, tol 15%)
wed [REDO] pricePerSeat 40 -> 44 (10.0%, tol 5%)
v2: Annual cost $65472. 74 of 124 seats used. Renew as is.
thu [ASK] no live value for usageRate
fri [REDO] usageRate 0.6 -> 0.41 (31.7%, tol 15%)
v3: Annual cost $65472. 51 of 124 seats used. Cut seats before renewing.
On Wednesday the price goes from 40 to 44, exceeding the 5% tolerance, rebuilding to v2 — cost rises but the conclusion still holds. On Thursday usage rate data is missing, and the monitor chooses to ask rather than reuse Wednesday's number. On Friday usage rate drops from 0.6 to 0.41, rebuilding to v3, and the recommendation flips to "cut seats before renewing."
Under a daily cron job, that would waste three rebuilds, while zero rebuilds would send out the wrong advice. Two rebuilds is the correct answer.
Where This Model Falls Apart
This is a teaching model, and real monitoring is harder. The hand-listed assumptions are only the tip of the iceberg — real financial analysis has dozens of hidden assumptions underneath, and extracting them is the hardest part. Percentage tolerance is too coarse — a small move near the threshold can flip a decision, while a large move far from the threshold may be irrelevant.
More critically: there's no provenance tracking, yet a real monitor needs to know where each live value comes from and how trustworthy it is; there's no human review, yet flipped recommendations should pass through someone before reaching the board; and a single report is a simplification, since real work products interlock, and one stale input triggers a cascade.
But the shape still holds: write down your assumptions, measure change, rebuild only when it matters, and ask when you can't tell.
The Bigger Picture: Delivering an Answer Isn't the End
report (v1)
└→ assumptions + tolerances
└→ live values arrive
├→ fresh: do nothing
├→ noise: log it
├→ stale: rebuild, bump version
└→ missing: ask
Analysis provides the answer, assumptions provide the receipt, tolerance provides the judgment, live values provide the reality check, versions provide the history, and humans provide the thresholds.
I think the useful capability agents need to learn next isn't writing better memos — it's noticing when last week's memo stopped holding. This demo isn't exciting, but it's much more useful by Friday. The work isn't done when the answer ships; it's done when the answer stops being true.
One-line Summary
Build a TypeScript report monitor based on assumption tolerances that rebuilds only when it truly matters and asks when data is missing.
Paragraph Summary
AlphaSense's SuperAnalyst keeps AI output working after the analysis is done, monitoring assumptions and updating conclusions automatically. This article uses a TypeScript miniature model to demonstrate its core mechanism: the report carries a tolerance for each input, and a four-state classifier (Fresh, Noise, Stale, Missing) decides whether a change deserves a rebuild. Fresh does nothing, Noise logs without acting, Stale triggers a rebuild and bumps the version, and Missing asks rather than reusing the old value. In the demo, a vendor renewal memo undergoes three rebuilds over five days, two of which are correct — a daily cron rebuild would waste three, while never rebuilding sends out wrong advice. The article also honestly notes the teaching model's limits: assumptions are hard to extract, tolerance is coarse, and there's no provenance tracking or human review — but the shape of "write down assumptions, measure change, rebuild on demand, ask when you can't tell" still holds.
Related articles

Cursor AI Programming in Practice: Build a Student Management System from Scratch in 30 Minutes
A Bilibili creator demonstrates Cursor AI programming: generating a student management system in 30 minutes with natural language. Details on Agent mode, Claude selection, prompt techniques, and thresholds.

Top 20 AI Coding Tools Ranking: From Rock-Solid to Lackluster, a Scored Matrix
Bilibili creator releases top 20 AI coding tools ranking, tiered T0–T4 from rock-solid to lackluster, with Claude Code and Codex tied at T0. This article details the scoring dimensions (capability, reliability, robustness, durability, cost, domestic availability) and tool performances to help developers choose the right AI coding assistant.

Vibe Coding Systematic Tutorial: Master AI-Assisted Programming from Zero
A three-month Vibe Coding tutorial from a Bilibili creator covering Codex, ClaudeCode, and Cursor, from core logic to commercial project practice for beginners.