Most AI answers are finished the moment they are written.
That is also the moment they start going stale.
The memo says "renew the contract."
Two weeks later, the price went up and half the seats sit empty.
The memo still says "renew the contract."
Confidently.
On October 6, 2026, AlphaSense announced SuperAnalyst, an always-on agent for financial and strategic work. It plans the work, reconciles evidence, runs code, and produces memos, models, and dashboards.
The part I keep thinking about is what happens after:
Unlike static AI outputs and deliverables, SuperAnalyst keeps working after the initial analysis is complete. It can monitor the assumptions underpinning an investment thesis, portfolio, transaction, or strategic decision; identify material changes in the market; and update the work product with new information, without requiring a new prompt.
That distinction matters.
The output is not the end of the task.
It is a promise that holds only while its assumptions hold.
An answer that can't tell you what it assumed can't tell you when it's wrong.
So let's build a tiny version.
By the end, you'll run one command:
npx tsx watch.ts
And watch one example memo stay put through noise, rebuild on a real change, and ask when data goes missing.
This is my small model of the idea. It is not how SuperAnalyst works inside. Every number below is made up.
Table of Contents
- The Problem
- Step 1: Write Down the Assumptions
- Step 2: Measure the Move
- Step 3: Classify the Change
- Step 4: Rebuild Only When Stale
- Step 5: Run It
- Where It Breaks Down
- The Bigger Idea
1. The Problem
There are two easy ways to keep a report current.
Rebuild it every day.
Or never rebuild it.
The first one burns compute and changes the answer for no reason.
The second one is how stale memos get approved.
The useful version sits in between.
Rebuild when something that matters moved. Leave it alone otherwise.
"Material" is the whole feature. Everything else is a cron job.
2. Step 1: Write Down the Assumptions
A report carries the inputs it was built on, plus a tolerance for each.
type Assumption = { key: string; value: number; tolerancePct: number };
type Report = { title: string; assumptions: Assumption[]; build: (v: Record<string, number>) => string; text: string; version: number };
tolerancePct is how far a value can drift before the conclusion is in doubt.
Price gets a tight tolerance. Seat count gets a loose one.
That is a judgment call, and it belongs to a human.
The build function is the analysis. Same inputs, same output.
3. Step 2: Measure the Move
function movePct(old: number, now: number): number {
if (old === 0) return now === 0 ? 0 : Infinity;
return Math.abs((now - old) / old) * 100;
}
Percent change, against the value the report was built on.
Not against yesterday.
That matters. Ten small daily drifts can add up to one big one.
4. Step 3: Classify the Change
type Check =
| { kind: "fresh" }
| { kind: "noise"; moves: string[] }
| { kind: "stale"; moves: string[] }
| { kind: "missing"; keys: string[] };
function check(r: Report, live: Record<string, number | undefined>): Check {
const missing = r.assumptions.filter((a) => live[a.key] === undefined).map((a) => a.key);
if (missing.length) return { kind: "missing", keys: missing };
const noise: string[] = [];
const material: string[] = [];
for (const a of r.assumptions) {
const now = live[a.key]!;
const pct = movePct(a.value, now);
if (pct === 0) continue;
const line = `${a.key} ${a.value} -> ${now} (${pct.toFixed(1)}%, tol ${a.tolerancePct}%)`;
(pct > a.tolerancePct ? material : noise).push(line);
}
if (material.length) return { kind: "stale", moves: material };
if (noise.length) return { kind: "noise", moves: noise };
return { kind: "fresh" };
}
Four outcomes.
Fresh. Nothing moved.
Noise. Something moved, but inside tolerance. Log it, don't act.
Stale. Something moved past tolerance. Rebuild.
Missing. A live value is gone. Ask, don't guess.
Missing is checked first on purpose.
A report that silently keeps its old number is worse than one that says "I don't know right now."
5. Step 4: Rebuild Only When Stale
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;
}
Only stale touches the report.
On a rebuild, every assumption is re-baselined to the live values.
The version number goes up, so anyone can see the answer changed.
Then the example inputs:
// ===== EXAMPLE INPUTS (made-up numbers, not real market data) =====
const memo: Report = {
title: "Should we renew the vendor contract?",
assumptions: [
{ key: "seats", value: 120, tolerancePct: 10 },
{ key: "pricePerSeat", value: 40, tolerancePct: 5 },
{ key: "usageRate", value: 0.62, tolerancePct: 15 },
],
build: (v) => {
const annual = v.seats * v.pricePerSeat * 12;
const used = Math.round(v.seats * v.usageRate);
return `Annual cost $${annual}. ${used} of ${v.seats} seats used. ${v.usageRate < 0.5 ? "Cut seats before renewing." : "Renew as is."}`;
},
text: "",
version: 1,
};
memo.text = memo.build({ seats: 120, pricePerSeat: 40, usageRate: 0.62 });
const days: [string, Record<string, number | undefined>][] = [
["mon", { seats: 120, pricePerSeat: 40, usageRate: 0.62 }],
["tue", { seats: 124, pricePerSeat: 40, usageRate: 0.6 }],
["wed", { seats: 124, pricePerSeat: 44, usageRate: 0.6 }],
["thu", { seats: 124, pricePerSeat: 44, usageRate: undefined }],
["fri", { seats: 124, pricePerSeat: 44, usageRate: 0.41 }],
];
const TAG = { fresh: "[FRESH] ", noise: "[NOISE] ", stale: "[REDO] ", missing: "[ASK] " } as const;
6. Step 5: Run It
Output:
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.
Here is what happened.
Monday: nothing moved. Fresh.
Tuesday: seats and usage drifted about 3 percent. Both inside tolerance. Noise, logged, no rebuild.
Wednesday: price per seat went from 40 to 44. That is 10 percent against a 5 percent tolerance. Rebuild to v2. The cost went up, but the conclusion held.
Thursday: usage data is missing. The watcher asks instead of reusing Wednesday's number.
Friday: usage dropped from 0.6 to 0.41. Rebuild to v3. This time the recommendation flips to "Cut seats before renewing."
Three rebuilds would have been wasted under a daily cron.
Zero rebuilds would have shipped the wrong advice.
Two was the right number.
7. Where It Breaks Down
This is a teaching model. Real monitoring is harder.
- Assumptions are listed by hand. Real analysis has dozens of hidden ones. Extracting them is the hard part.
- Percent tolerance is crude. A small move near a threshold can flip a decision. A big move far from one may not matter.
- No sources. A real watcher needs to know where each live value came from, and how much to trust it.
- No human review. A flipped recommendation probably deserves a check-in before it goes to the board.
- Single report. Real work products depend on each other. One stale input can ripple.
The shape still holds.
Write down what you assumed. Measure the move. Rebuild only when it matters. Ask when you can't tell.
8. The Bigger Idea
report (v1)
└→ assumptions + tolerances
└→ live values arrive
├→ fresh: do nothing
├→ noise: log it
├→ stale: rebuild, bump version
└→ missing: ask
The analysis provides the answer.
The assumptions provide the receipt.
The tolerances provide the judgment.
The live values provide the reality check.
The version provides the history.
The human provides the thresholds.
This part is prediction, not history.
I think the next useful thing agents learn is not writing better memos. It is noticing when last week's memo stopped being true.
That is less exciting to demo.
It is a lot more useful on a Friday.
The job isn't done when the answer ships. It's done when the answer stops being true.
Try Roster
I'm building Roster around this idea: AI employees with real responsibilities, tools, memory, schedules and computer access. They keep watching the work after it ships, and they tell you when it changed.
If the same follow-ups, handoffs, and waiting loops keep eating your week, give them to an AI employee.
Top comments (0)