Tristan de Halleux
← Projects

PROJECT / 2026

Dash

A private operating system I built to centralize the different systems of my life.

Dash started because I wanted a finance dashboard across all my bank accounts. Three weeks later I had voice input, task management, calendar sync, health ingestion, semantic memory, agentic review queues and 24/7 background jobs.

Explore Dash ↗

This version runs on synthetic data

Dash Home in dark mode, aggregating today's tasks, calendar, health and private finance overview using synthetic data.

01 / THE SYSTEM

One state layer for everything

Data inputs to the system flow through server-owned adapters where they are normalized into structured records in Postgres, then they can be surfaced through Home, Tasks, Health, Finance, Review, or queried through Brain. I kept as many calculations, rankings and canonical records deterministic, with AI models being called for fuzzy inputs such as classification, embeddings, or search.

Importantly, the models can interpret data, but not edit the original inputs.

I started out by hosting the system and scheduled jobs in the cloud and eventually started moving the heavier workflows toward a private always-on worker.

System topology / data ownershipSolid: operational · dashed: later direction

Capture

Web capture · Telegram / voice

Calendar

Google Calendar

Health

Apple Health · Strava / Hevy imports

Finance

Plaid · local/private imports

Node server / trust boundary

API routes · authentication · single-user / owner identity · input validation

Server-side secrets · connector credentials · adapters · policy / action gating

React browser → Node/server API → application/domain logic → Supabase

Capture / CRM

Captures → classification / extraction → confidence / Review → tasks, people, goals, decisions, links

Health

Ingestion → sample normalization / reconciliation → raw / canonical samples → rollups / daily projections

Calendar

Incremental sync → cached calendar state → contextual read models

Finance · protected

Finance module → reconciliation → deterministic balances, liabilities, utilization, investments

Supabase / Postgres + RLS

Durable structured application state

Tasks · captures · people · goals · decisions · links
Health samples / rollups · calendar cache / events
Reviews · notifications · audit / action state

Domain-owned records; distinct reconciliation and access semantics.

Deterministic logic

Finance math · task ranking · queue logic · health rollups · read models · derived state

↓ server-owned outputs

AI interpretation

Classification · field / people / fact extraction · review refinement · embeddings · search / Ask synthesis

↑ structured output → policy gate → stateOriginal inputs stay intact.

Memory / Brain

Sanitized summaries / memory_chunks → embeddings → pgvector → similarity search → cited Ask

↑ source references to canonical recordsSecondary retrieval index

Read models → product surfaces

Home: cross-domain aggregator
Tasks · People · Health · Finance · Review · Brain · Settings

React / TypeScript / Vite UI

Consumes server-owned read models through the Node API.

Uncertain / low confidence

Policy gate → Review

Safe / reversible internal actions

Controlled execution → undo / audit where supported

02 / STRUCTURED STATE

Turning messy inputs into something I could trust

I imagined the system as being able to speak into my phone anytime I wanted and say something like "I need to finish the mechanics problem set Friday" or "Just met Costanza from IBM" and it would automatically go where it belonged.

The model pulled structure out of the sentence—what it was, when it was due, what it related to—then Dash would store the hard deadline, decide the day I should focus on it and its current priority. That meant ordinary code would rank the queue, and if a capture was uncertain, it would route to Review instead of silently committing it.

  1. capture
  2. classify
  3. extract
  4. confidence
  5. task state
  6. rank
  7. queue
Capture dialog accepting text or voice, with Auto, Task and Note modes and a reminder that uncertain items go to Review.
One input. Structure comes after capture.
Synthetic task queue grouped into overdue, today, this week and waiting, with priorities and due dates.
Deterministic state becomes a ranked queue.

An update isn't always an event

My health data broke the first version of that system because repeated Apple Health exports looked like new records, but they were really just updated snapshots. Because my system was treating every upload as append-only data, there were duplicates everywhere and rollups no longer matched reality. To fix this, I added a decision matrix for what an incoming record meant—event, update, or snapshot—and reconciled it before deriving the daily state.

Health overview showing synthetic steps, sleep, habits and a fourteen-day sleep projection derived from reconciled data.
Daily state depends on what an upload means.
Wrong / append-only
  1. snapshot
  2. append
  3. upload again
  4. append
  5. double count
Right / reconcile first
  1. snapshot
  2. reconcile
  3. canonical samples
  4. daily rollup

03 / MEMORY + AUTONOMY

Useful AI needs boundaries

The Brain is my attempt at building a vector DB of my life. I embedded sanitized summaries of existing state into pgvector, then kept references back to the original records, which allowed me to create a chat where answers can cite the original input they were using.

  1. canonical state
  2. sanitized memory chunks
  3. embeddings
  4. pgvector
  5. similarity search
  6. cited answer
Brain search for atlas returning a capture and related tasks with source types and dates.
Known-result search · cited answers · no-evidence refusal · suggestions couldn't mutate state

Adding an agent automation layer created a new problem: being able to do something didn't mean Dash should deploy agents automatically. I solved this by implementing a confidence scale where reversible actions could move on their own, but uncertain or more consequential changes went through Review, with thresholds and undo built into the action flow.

inputclassify + extractconfidence
↙
review
↘
safe action
↓
undo / audit
Automation settings showing paused execution, Review first policies, Auto when certain options and advanced confidence thresholds.
Autonomy is a policy decision, pipeline by pipeline.

04 / PRODUCTION

The one-minute sync that wrecked the database

I had about twenty calendars to keep fresh and Google's API quotas made a one-minute sync look cheap.

Turns out I was measuring the wrong system.

There was a huge bottleneck downstream, where scheduled functions started overlapping, making Calendar consume roughly 83% of the system's entire scheduled runtime while the database was doing an absurd amount of repeated work: roughly 47.6 million calendar_events updates against only ~4.3K live rows.

calendars
~20
initial cadence
1 min
runs / 6 hours
100
timeouts
99
scheduled runtime
~83%
event updates
~47.6M
live rows
~4.3K
  1. Google quota looked safe
  2. poll every minute
  3. overlapping jobs
  4. repeated DB writes
  5. CPU / I/O saturation
  6. stop + recover + slow down

So I shut the sync down, let Supabase recover, dropped the cadence to every fifteen minutes, and now I know not to size a system against just the API quota.

05 / WHAT I LEARNED

I added integrations faster than I defined to the existing system what they meant

Looking back, this was clearly the recurring mistake. I kept adding APIs because it was easy, but then failed to immediately define to the system whether their data was authoritative, whether a repeated value was an event or a snapshot, how to reconcile conflicts, and what to do if that part of the pipeline failed.

  1. source identity
  2. idempotency
  3. event / update / snapshot
  4. normalization
  5. reconciliation
  6. provenance
  7. failure state

Planning out integrations involved more than just connecting APIs; it's about deciding how an external fact is allowed to become an internal truth.

This system is retired.

By October I wasn't actively developing Dash anymore, but keeping the live version meant maintaining its database, credentials, scheduled jobs, and private integrations. So I decided to retire the backend and rebuild the frontend as a synthetic archive instead. It looks and behaves like the final Dash UI, but it is no longer connected to my live data or private services.

Explore Dash ↗
The retired Dash interface preserved as a synthetic archive, showing Home in light mode.

↑ ↓ to move ↵ to open

A few shortcuts

Open command palette
⌘ / Ctrl K
About
a
Projects
p
Ask
/
This reference
?
Close a dialog
Esc
~/tristan
A small door. Type help.