A skill an agent can call, not an app you open.
LifeOS Inbox reads a mailbox and answers a small set of questions about it: what do I owe, who owes me a reply, what closes soon, and what needs me today. It is a typed capability surface plus a scheduler — the user interface is the least important part of it, and mostly optional.
The repo is roughly 5% presentation and 95% engine. Every top-level export is a pure function from a mail corpus to a typed answer:
triage(signal) // one message → category, urgency, action, evidence
classifySignal(signal) // the above, escalating to a model only if needed
buildMoneyView(signals) // bills, renewals, what is overdue, what is at risk
buildFollowUpView(signals) // open loops, silence timers, dead channels
buildRadar(signals, obligs) // every dated commitment on one timeline
buildBriefFacts(signals) // the morning brief, as data
whichCard(category) // "which card for dining?" — from your own rules
recordCorrection(signal, fix) // teach it; the fix outranks the model permanently
computeMetrics(signals) // can this be trusted yetThat shape is deliberate. An agent can call these; a cron can call these; a web page can call these. Nothing in the core knows or cares which.
Three things are not skill-shaped and need a service behind them:
| Why | |
|---|---|
| Push | The 07:00 brief has to arrive whether or not you thought to ask. A capability that waits to be invoked is one you will never invoke. |
| State | Corrections, promoted rules, silence timers, the ledger overlay. A skill that forgets between calls cannot track a follow-up at all. |
| Determinism | Most classification is deliberately model-free so it answers the same way every run and corrections accumulate. Re-reasoning conversationally each time loses that. |
Gmail ─┐
├─► intake ─► normalise ─► ① rulepack ─► ② model ─► decide ─► surfaces
capture┘ (Signal) (free) (fallback) brief · ledger
desk · radar
Tier 0 — deterministic. A rulepack of sender rules, per-issuer parsers and regex extractors. Free, instant, and it gets larger over time: three consistent corrections for a sender promote into a permanent rule, so the model's share shrinks with use rather than growing.
Tier 1/2 — the model, only for the residue. Claude Haiku for what the rules could not resolve, Sonnet for the ambiguous tail. Closed enums, structured output, and every extracted value must quote verbatim evidence from the source or it is discarded — which makes an invented due date structurally impossible rather than merely unlikely.
Redaction runs before anything leaves the machine. Card numbers are masked behind a Luhn check, account numbers become salted tokens, Aadhaar is matched on its Verhoeff checksum, tax IDs are removed outright. A message containing a one-time code is never transmitted at all.
Modules read core signals and own their own state. Core never imports a module — that direction is the whole extensibility seam.
- Money Ledger — obligations derived from signals, merged with lineage, plus a canary that alarms when a bill source goes silent. Absence detection: a ledger that has gone blind looks exactly like a month with no bills.
- Follow-Up Desk — entirely deterministic. Bounce aggregation, ticket IDs, silence timers, reply-close. No model is asked whether a message expects a reply.
- Deadline Radar — date-window extraction. A voting window closes on a date that is not the one it opens on; the closing edge is the deadline.
Picking this up after a break? Start with
docs/STATE.md.
This has never run on live mail. Not once. Every number below is measured against a fixture corpus.
| Tier 0 on hand-picked fixtures | 97.9% |
| Tier 0 on an unbiased slice | 72.0% |
| Both measured on | Gmail snippets, not message bodies |
The fixture corpus contains no message bodies — only ~125-character previews. So
the HTML/MIME layer has never been exercised, and both classes of bug found so
far were body-layer defects. Treat every accuracy figure here as provisional and
read docs/measured-accuracy.md before quoting one.
The residue costs roughly $1.25/month through Haiku, against a planned ₹850 ceiling — so 72% is comfortable, not alarming. It is a floor: it rises as corrections become rules.
44 acceptance checks · 75 unit tests · typecheck clean
Zero runtime dependencies for the core — Node 22.18+ strips TypeScript natively, so there is no build step.
npm ci
npm run typecheck && npm test
npm run auth # one-time Google consent (see docs/week-1-setup.md)
npm run backfill # last 30 days
npm run triage # what the pipeline makes of it
npm run brief:dry # the morning brief, printed
npm run money # bills and renewals, worst first
npm run loops # open loops and dead channels
npm run radar # every dated commitment on one timeline
npm run metrics # can this be trusted yet
npm run review # what it could not resolve
npm run correct -- <id> bill today # fix one; it outranks every future runPersonal configuration (owner.json, cards.json), ingested mail (data/),
credentials and recorded model responses are all gitignored and never leave the
machine. See *.example.json for the shapes.
The capability surface is exposed to a Hermes agent — a local, skill-loading
agent — as a skill. integrations/hermes/SKILL.md teaches the agent when to reach for
LifeOS, which commands answer which question, and — importantly — how to report
the result honestly: quote its figures exactly, never claim a bill is paid
(LifeOS sees mail, not bank accounts), and always surface the canary alongside a
total, because a blind ledger looks exactly like a month with no bills.
./scripts/deploy-to-hermes.sh # local Hermes
./scripts/deploy-to-hermes.sh root@your.vps.ip # over SSHThe script is idempotent, checks the Node version, never overwrites an existing
owner.json/cards.json, and never moves credentials.
These are load-bearing, not preferences:
- Read-only.
gmail.readonlyand nothing else. All triage state lives in our own store. A triage layer that cannot hurt you is one you will let be wrong. - Advise, never execute. Payments are deep links, never API calls. Drafts are never sent.
- Corrections outrank models, permanently, and are consulted before any call.
- Urgency floors escalate and never demote. A false urgent costs a glance; a missed urgent is how a triage product dies.
- Nothing is dropped silently. A parser that matches a money sender and reads nothing raises an alarm rather than filing it quietly.
MIT.
