An executive assistant for your whole life.
Almanac is a single system of record for your goals, intentions, tasks, notes, spending and the people in your life — and a reasoning layer that works across all of it on your behalf.
Say one sentence and it files itself, dated and linked to everything related. Ask for a plan and it builds the whole thing — tasks and events in Google Calendar and Tasks, drafted email, a working document in Docs, research pulled from the open web — then tracks what you actually do against it, scores what is slipping, and each morning reads the whole picture back in a few sentences. The rest of the day it stays quiet unless something genuinely needs you.
This page is a walkthrough of how each part works. Every section opens up into the mechanism behind it, so you can read it either way.
01 · Capture
One sentence, typed or spoken, gets parsed into the right kind of record, given a real date, written to your calendar, task list or notes, and attached to anything it mentions. There is no form to fill in and no category to choose.
What was said, Tuesday, 3 June, 4:12pm
“Remind me to send Priya the partner deck tomorrow, and dinner with Dana is Thursday at 7.”
Dates fixed at the moment of capture
Filed
Tap the three above to see what each kind of sentence becomes.
Capture runs through a phone shortcut, so a thought can be recorded from the lock screen without opening anything. It accepts typed text or a voice recording, transcribes the audio, and posts it to a single endpoint. A first pass classifies what kind of thing was said; a second extracts the fields for that kind.
Relative dates are resolved at the moment of capture and stored as absolute dates. This is the difference between a system that works and one that quietly lies to you: store the phrase instead, and something reading it back four days later has no idea when it was written and will repeat the word “tomorrow” about a deadline that has already passed.
A deduplication pass sits in front of the write, so saying the same thing twice in a week produces one record plus a note that it has now been said twice, which is itself worth knowing.
02 · Connections
Most tools keep tidy separate lists: contacts in one place, spending in another, notes somewhere else. The useful information lives between them. That a charge paid for a specific project, that an evening belongs to a specific person, that one workspace is where two different things actually get done.
Fifteen records from nine different tables, sitting at different depths because a real one has no single layer. Tap any of them: a charge reaches the project it paid for, a task reaches the person it is owed to, a memory about someone reaches the evening it explains. Nothing here was tagged by hand.
Tap any node, near or far. Tap it again to clear.
Edges live in a single table keyed on(from_type, from_id, to_type, to_id, relation) and written with an upsert, so extraction can be re-run over the same text as often as you like without multiplying the graph.
Extraction matches text against records that already exist, on whole-word boundaries, and never on a model's impression that two things feel related. Names under three characters are ignored outright, because the false-positive rate on those swamps anything they find. A wrong edge is worse than a missing one, since everything downstream treats edges as fact.
Lookups return both directions and preserve which is which. “This note mentions a contact” and “this contact is mentioned by a note” are the same edge but not the same sentence, and a briefing that confuses them reads as nonsense.
03 · Patterns
A set of detectors walks the connections on a schedule, looking for specific shapes that mean something. When a shape is not there, they say nothing, which is most days. What you can ask it is only half the picture — this is the half where it comes to you first.
relationship_debt
A contact you set a check-in cadence for has gone quiet, and you are about to see them anyway.
19 days since last contact against a 14 day cadence, with a meeting on Thursday.
contradiction
Two things recorded at different times cannot both still be true.
A stated finish date in March and a dependency that does not land until June.
project_cost
Real money has gone into something not being tracked as an expense.
A $146.80 charge matched to a project with no recorded budget.
concentration
One thing is quietly taking a disproportionate share of your time or your spending.
Four of the last six evening blocks went to the same stalled project.
When one of these actually clears the bar, it is delivered as a real push notification to the phone's lock screen, not just something waiting the next time the app happens to be opened.
Each detector is an ordinary function over the graph rather than a prompt. It either matches a shape or it does not, so any finding can be traced back to the exact records that produced it, and the same input always produces the same output.
A model is involved only at the final step, to phrase a finding that has already been established as true. Findings are then deduplicated against what has recently been delivered and passed through the same interruption budget as everything else. A pattern being interesting does not exempt it from being an interruption.
04 · Daily briefing
Not a list of your events read back to you. Every figure is calculated before a model sees anything, and the model is asked only to decide what actually matters today and say it in three short paragraphs.
Assembled in code, with no model involved
TODAY: Thursday, 5 June 2025CALENDAR (5.5h committed across 4):[meeting] 9:00am-10:30am Partner sync with Priya[block] 11:00am-1:00pm Onboarding rewrite[meeting] 1:00pm-1:45pm Design review[personal] 7:00pm-9:00pm Dinner with DanaOVERLAPS: "Onboarding rewrite" runs into "Design review"OVERDUE (1): Send Priya the partner deck (due 2025-06-03)OPEN TASKS: 14QUIET CONTACT: Priya, 19d since last contact, cadence set to 14d
The partner deck was due Tuesday and Priya is on your calendar at nine, so that gets settled before it turns into a conversation about why it wasn't.
The two hours you blocked for the onboarding rewrite run straight into the design review at one. One of them has to move, and the review is the one with other people's afternoons attached to it, so move the rewrite.
Nineteen days without contact and a meeting on the books is not a coincidence worth letting her notice. Everything else today is genuinely fine.
Calendar, tasks, contacts, projects, open intentions, mail and cross-domain signals are gathered in parallel. Each source settles independently, so one slow upstream call degrades a single line rather than blanking the whole briefing.
Event kinds are classified in code as well. A meeting with other people, an appointment, an hour set aside for yourself and a bare reminder are not the same thing and must not be described as if they were. Overlaps are detected before the prompt is built, so the model states a collision as fact rather than trying to spot one.
The instruction is explicit: do no arithmetic. A model that does its own arithmetic will occasionally be wrong and will always sound certain, and a briefing you have to double-check is worth less than no briefing at all.
05 · Interruptions
Anything that can reach your lock screen has to earn it every time. Candidates are generated freely, and most of the work goes into throwing them away.
Hard limits rather than heuristics: at most two messages per run, and never two about the same intention inside three days. Candidates are batched and deduplicated before anything is sent, which is what prevents the obvious failure mode of three notifications about one deadline arriving before breakfast.
Every kind of message declares a minimum urgency tier, and those tiers are matched against your notification setting through a shared table rather than compared as free-form strings. Two vocabularies drifting apart is exactly how a system ends up silently delivering nothing while reporting that it is set to deliver everything.
Delivery windows are set per kind. A check-in about a person goes out in the morning; a reminder about an evening block goes out in the late afternoon. Nothing is queued overnight, because a notification read at seven in the morning in a stack of six is a notification you dismiss.
06 · Spending
Bank feeds arrive as a merchant string and an amount, with no category attached to them. Every transaction here is classified on arrival, ranked by size, and checked for the charges that repeat.
Last 30 days, sorted automatically
Ranked
Sample figures. In the live app these are drawn from your connected accounts, refreshed twice a day and re-sliced locally for whatever window you ask for. The transactions behind them are the same records the graph two sections up connects to projects.
Classification is rule-first and model-second. A merchant is matched against an ordered rule list, and only an unrecognised one is escalated to a model, whose answer is then cached so the same merchant is never paid for twice. Rule order carries real meaning: fuel has to be tested before groceries, or a supermarket-branded petrol station gets filed as food.
Repeating charges are found structurally, by looking for the same merchant at roughly the same amount more than once, rather than by matching against a list of known subscription names. A list only ever finds the services someone thought to add to it.
Account data is pulled on a fixed schedule, cached, and then sliced locally for whatever window the page asks for. Every figure on the page is computed server-side, and no model produced any number on it.
07 · How it is built
Described by technique rather than by product name. Everything listed here runs today.
They were two. Every page load meant a request from the browser to the interface and a second network hop from there to the API, six of them on the home screen alone, which put most of a second into every navigation purely in the app talking to itself.
Merging them turned those hops into in-process function calls. The request handlers moved across unchanged behind a small adapter rather than being rewritten, because roughly 1,450 lines of already-debugged request handling is not something to retype for tidiness. The adapter is the only new surface, which puts all of the risk in one file that can be tested directly.
This walkthrough is prerendered at build time and performs no data access of any kind. It is the only route outside the session check, so rather than trusting it to behave, a test walks its imports and fails the build if any of them can reach the database, a connected account or a calendar. Every figure you have read on it is invented.
Motion is gated the same way. Entrances start hidden and are revealed by script, which means three separate ways a visitor could be left looking at a blank page: a reduced-motion preference, scripting disabled, or no intersection observer. Each has its own explicit override, and two of them are covered by tests, because the failure is silent and looks like an empty screen rather than an error.