V
An assistant on my own email that decides what is worth interrupting me for, and stays quiet the rest of the time. It is now being rebuilt as a company of AI agents.
Why I built it
I had already built a few systems where a team of AI agents works on its own, each with its own job. V was the natural upgrade of those, pointed at one concrete job: my email.
What it is
V reads my email and works out which messages are worth bothering me about. The aim is that most days the honest answer is very little, and that is the point. Anything can ping you for every email. The hard part is looking at a few hundred messages and spotting the one that has a consequence, like a reply I promised and never sent, while leaving the rest alone.
Think of a good assistant sitting next to you in the morning. They do not read you your inbox. They say "this one, today, and here is why", and then nothing for the rest of the day. That is what I was trying to build. I ran it on my own Gmail, so when it got something wrong, it was wrong about my real mail.
What it does
Every morning V went through my recent mail and narrowed it to the threads that might matter. Then it asked a language model, Claude, to judge each one: does this have a consequence, and why. Whatever earned a place went on a page I call the Day Ahead, and could be pushed to my phone. I could answer back on that page.
If an email needed a reply, V could write a draft. Nothing is ever sent until I tap approve. The talking part, the voice you can speak to, is a separate project of mine called VibeOS. V is only the thinking behind it.
What happened
For a long time I could not tell whether it worked. On 12 July the whole thing went through three separate reviews, and all three came back with the same verdict: it talked like an assistant but behaved like a notification app. Every morning it forgot yesterday's conclusions and worked everything out again from scratch. So I rebuilt the middle. Each judgment is now saved, and if the email has not changed, V reuses what it decided before.
Then I built a way to check it, a small separate tool called proof. The idea is simple. Keep track of the things V did not flag too, then see whether the flagged ones really went wrong more often. On one test, "has this sender gone quiet?", they did.
That is a real gap, but I want to be straight about what it shows. Most of those senders are companies, so it is closer to "will this service email again" than to anything about people. On the question I actually care about, whether I will keep a promise I made, it found nothing, and when I dug into why, the thing I was measuring with turned out to be partly blind. That is the kind of result I would rather know about than not.
What's next
I no longer run V every day. I am working on an upgrade instead, and it is a bigger idea than a better inbox filter. It has newer models, a separate model whose only job is to make decisions quickly and cheaply, and decisions for every agent that rest on unbiased statistics instead of on what a model happens to feel that morning.
The larger plan is to turn it into a small company of AI agents. Each agent has a role, like a CEO, a CTO, a CFO and an HR agent. The CFO handles the budget, meaning how much the agents are spending. HR can create new agents, let some go and keep track of how each one is performing. I am the board, and the CEO reports to me. I am designing it carefully before building it, and the first pieces of code exist.
V itself was left with two open problems. It needed far more feedback from me: in July I counted somewhere between 15 and 25 times I had told it whether it got something right, which is not a lot to learn from. And it treated everyone at a company as one sender, so it could not tell when a particular person had gone quiet.
What I wanted
Notifying is easy. Deciding that one email out of a few hundred has a consequence, and that the rest can wait, is the hard part. I wanted an assistant that keeps a model of my situation, says nothing most of the time, and speaks up when something I would otherwise miss is about to cost me.
V came out of the autonomous agent-team systems I had already built. It was the natural upgrade of those, narrowed to one job. It is only the brain. The voice it talks through is a separate project of mine, VibeOS, which connects to V over a websocket. I ran V on my own Gmail and calendar, so its mistakes landed on a real inbox. There is one user, and that turned out to be the actual difficulty: building it was the easy half, and knowing whether any of it works was the hard one. I built it between late June and late July 2026, and I no longer run it daily.
How it works
As I left it, V is a set of scripts on one Windows machine, not a service. A scheduled job ran the morning pass at 7am, there are some manual commands, and a small localhost server serves the page. (That job died without logging a single line on the runs from 4 to 10 July. Failures now land in a log.) A daemon that polls mail exists, but it is not the path I used. Between runs, V senses nothing.
The morning pass fetches between 150 and 2,300 recent messages. Each detector narrows candidates with a regex or a count, then makes one stateless Claude call per candidate at temperature 0. The call returns a verdict, a 0 to 1 consequence score and a sentence of reasoning.
What does temperature 0 mean?
Temperature controls how much randomness a model adds when it picks its words. At 0 it takes the most likely option every time, so the same input should give the same answer. It is not a guarantee, and V's early verdicts still flipped between runs, which is a big part of why I changed the middle.
The part I changed most is the store in the middle. Originally every verdict was computed, rendered and thrown away, and I patched the run-to-run flapping by asking three times and taking the majority. Now a verdict is a durable row keyed by a hash of the evidence it was judged on, and unchanged evidence reuses the old conclusion. A steady-state run reuses nearly all of them.
What is an evidence hash?
A hash turns a thread and its latest message into a short fingerprint. The same input gives the same fingerprint, and a new reply gives a different one. The date is left out on purpose, so a thread does not look new just because another day has passed. V compares fingerprints to decide whether anything it would be judging has changed since last time, which is cheaper and steadier than asking the model again.
The output is a Day Ahead page I can answer back to. V can draft an email reply, but nothing sends without my explicit approval. The recipient is bound to the verified sender of the thread, automated addresses are blocked, anything about money or law is forced to ask first, and sends are idempotent. The voice path has no send operation, and that is not a check I could get wrong: the type its calls return simply does not contain one. In July the send path had been used about once.
Measuring it
I keep a denominator. V writes a row for every candidate it considered, flagged or not, and the unflagged rows are my control group. The measure is whether flagged items go wrong more often than unflagged ones. That harness is now its own package, proof, which V uses. It refuses to print a percentage below an evidence bar, and it counts distinct subjects, not rows.
What is an oracle, here?
Just whatever tells me what actually happened by the check date. For "did this sender write again" it is a lookup in my mail, so it cannot guess. For "did I deliver what I promised" it has to read conversations, and it cannot see anything that happened on a channel V cannot read. That difference ends up mattering a lot below.
What are lift and percentage points?
Lift is how much more often the flagged items went wrong than the unflagged ones, subtracted as plain percentages. Going from 36% to 68% is a gap of about 33 points (the figures are rounded, so a point can go missing). Separation is the threshold-free version: it looks at the judge's raw scores instead of the yes/no flag, so it does not depend on where I put the line.
| Test | Result |
|---|---|
| Gone quiet? | Flagged 68% vs 36% unflagged: +33pp, separation +0.10 |
| Gone quiet, one row per subject | +19pp (first draw), +31pp (last draw) |
| Gone quiet, vs the old formula | 33pp vs 26pp on identical rows: inside the noise at this size |
| Gone quiet, by subject type | 48 domains: +27pp over 95 judgments. 3 people: +50pp over 5 judgments |
| Deliver a promise, run 3 | Flagged 64% vs unflagged 75%, separation -0.03: no signal |
| Same, after the oracle fix | Coverage 11 of 11, but only 4 gradeable promises: not enough evidence |
| Promise extraction | From sent email and chat: 226 messages, 20 promises, 17 verified kept or moot, 3 open |
| Day Ahead from stored state | 38% of attention items in one real run in late July, against a 50% target |
The flag line for the +33pp test was inherited from the promise work, not fitted on it. The slider above shows how much the number moves depending on how I cut it, so treat +33 as a rough size.
I also ran proof on public GitHub issues (vercel/next.js), a domain it was not built for. A hand-written scoring rule, not a model, gets a +9pp lift over 206 issues, and a one-line age heuristic gets the same 9pp, so the harness says the richer rule is not earning its cost. The proof project has that run.
What didn't work
On 12 July I wrote down, as neutrally as I could, what the code actually does, and it then went through three separate reviews. The three converged: V had an assistant's vocabulary and safety instincts wrapped around a notifier. Every judgment was a stateless call, thrown away after rendering. One review counted eight episodes in the log, about three of them real feedback, and a self-graph whose 15 rows all came from the seed function. The rebuild described above is my answer to them.
A metric I built to say how much of a page was caused by a change in the model was wrong. A truncation bug made two threads collide on one verdict row, which manufactured "flips" that never happened, so on 16 July I deleted the metric instead of patching it.
My oracle could not see anything that happened on a channel V cannot read, so on 11 of 13 failure labels "I never delivered" looked the same as "I delivered by hand or in person". That means the "no signal" result on promises was a statement about my instrument, not about the judge. Run 2 had a separate bug: promises tied to a date, like a call or a move-out, were closed when the date passed and scored as delivered.
Three attempts at learned memory (JEPA-style latent models among them) lost to simple methods at n=1. Silencing mail on sender relationship would have skipped the model call on only 4 of 32 emails, and rested on verdicts that flipped between runs, so I kept escalating instead. And a daily cycle once wrote ten predictions, deleted them and reported success, because three open stores each rewrote the whole file. Stores now merge on write.
What is unfinished
I no longer run V daily. I am working on an upgrade, and it is a different shape from an inbox assistant. It brings in newer models, plus a separate decision model whose job is speed and efficiency: making each call quickly and cheaply. The agents' decisions also move onto unbiased statistics rather than a single model's opinion.
The bigger idea is to run the whole thing as a company of AI agents. Each agent has a role (CEO, CTO, CFO, HR and so on). The CFO handles the budget, meaning how much the agents are spending. HR can create new agents, lay some off and track how each one performs. I am the board and the CEO reports to me. I am designing it carefully before building it, and the first pieces of code exist.
As for V in its original form, most of what surfaces is still triggered by an email scoring high. The 38% above is one run. Feedback is the binding constraint: in July I counted somewhere between 15 and 25 explicit feedback events in total.
The cadence layer is dominated by automated senders and keyed by organisation, so person-level joins mostly cannot work yet. Some of what V knows about me is typed into the source instead of learned. A full morning run cost about $2 when I measured it on 12 July, before verdicts were reused. The tests cover the pure functions, send safety and the stores, not what the model decides. Multi-user isolation exists but has never been tried with a second person.
- Stack
- TypeScript, Claude, IMAP and iCal, JSONL stores
- Status
- In progress
- Measurement
- proof, a separate package, extracted on 28 July
- Code
- Not public (V, proof, VibeOS and the upgrade)