← Projects Running daily 2025–

Alter

Drop a thought. Alter knows what to do with it.

Try Alter 14-day free trial · no card needed

  • 11services
  • 289Klines of TypeScript
  • 40%of it is tests
  • 5rewrites in 18 months

Where does a messy thought go?

you drop

“book the tuesday flight before the price moves — and mum's birthday is that same week”

voice · on a walk · 08:14
alter reads it
  • taskthe tuesday flightTurkey trip · due Tue
  • remindermum's birthdayFamily

receipt filed under Turkey trip — you've been planning flights all week. undo

the two questions
today · what needs you Book the Tuesday flight promoted: price-sensitive, due this week
life · where everything stands
  • Turkey trip
  • Family
  • Alter
  1. 01 of 04

    A thought, as it comes.

    You say it and keep walking. You don't file it, tag it, or open the right list first. Two things, tangled in one sentence, is how thoughts actually arrive.

  2. 02 of 04

    Alter reads it.

    It finds a task with a deadline and a reminder in the same sentence, and files each where you have been working. Watch the two fragments leave the sentence. Nothing structural is invented: a new project can only ever be proposed, never created behind your back.

  3. 03 of 04

    It files the flight under the wrong trip. What does putting that right cost you?

    • A form
    • One tap
    • Nothing: it's lost

    One tap. Every filing leaves a receipt that says where it went and why, with undo sitting next to it. Being wrong is that cheap, which is the only reason filing can happen without asking first.

  4. 04 of 04

    The only two questions.

    What needs you today, and where everything stands. The Tuesday flight is promoted because it is price-sensitive and due this week; the rest waits where it was filed.

Live, paid, and the thing I open first in the morning. I typed almost none of it.

Three things that broke. What would you have done?

What broke

"Shipped" didn't mean "works".

What would you have done?Why, the fix, and what it changed
Why

Every service's own tests passed and CI was green, but nothing exercised the composed system: real services talking to each other, real models, streaming over the network.

Fix

Trust the running stack, not the mocks: end-to-end paths against the real composition, and an acceptance tester that only ever sees staging.

Result

A feature can no longer clear every check and still be broken end to end.

The full story
What broke

A prompt that had run fine for weeks started coming back refused, a categorical decline of the whole task class.

What would you have done?Why, the fix, and what it changed
Why

A routine model change, on the provider's side. Prompts aren't code: they don't break loudly and they don't break deterministically.

Fix

A reframe, plus a fallback to a second model whenever a refusal is detected.

Result

Prompts are treated as dependencies I don't control: versioned, golden-tested, with a dollar cap per run.

The full story
What broke

Five modes, five surfaces, and a new user had to learn about fifteen concepts before the app made sense.

What would you have done?Why, the fix, and what it changed
Why

The information architecture gave system processes their own rooms, when the user only ever had three questions: where did that go, what needs me now, where does everything stand?

Fix

Five pages became two tabs. The modes went internal (recognition picks one and shows its choice as a chip you can flip), and receipts went everywhere, because invisible filing is filing you can't trust.

Result

Onboarding is three screens. Fewer rooms, more evidence.

The full story
The deep dive
  • TypeScript
  • NestJS
  • pgvector
  • Redis
  • Docker

I’ve been rebuilding one idea (a place that actually understands the things you throw at it) under one name or another for a year and a half. Five rewrites later, this is what it became. (The whole story, if you want it.)

One dock in, two questions out

You drop a thought into a dock that’s always there: a note, a question, a half-formed idea, a voice ramble on a walk. Alter reads it, decides what it is, and files it where it belongs, then tells you what it did and why: filed under Turkey Trip. You’ve been planning flights all week. One tap undoes it.

Everything after that is two questions, which are the only two the app has:

  • Today: what needs you now. A short, chosen few, promoted each morning, with the reasoning behind the choice visible rather than mysterious.
  • Life: where everything stands. Your whole workspace at a glance: one tile per endeavour, weighted by how live it is, in a map that doesn’t rearrange itself while you’re looking at it.

Underneath, a wiki compiles itself: once a few captures accumulate against a topic, a worker compresses them into that page (always preserving what you wrote by hand) and re-indexes it for search. (The idea of an LLM quietly keeping a clean, self-compiling knowledge base out of a messy stream of inputs is one I took from Andrej Karpathy.)

Five modes were the product’s headline, and the problem

For most of this year the product’s headline was five modes: five different things a capture could become, each with its own surface, each explained to you during onboarding. I was proud of them. They were also the problem.

A new user had to learn about fifteen concepts before the app made sense, against a working-memory budget of about four. The information architecture gave system processes their own rooms (a page for the inbox, a page for the wiki, a page for briefings) when the user only ever had three questions: where did that go, what needs me now, and where does everything stand?

So in August the whole surface came down. Five pages became two tabs. The modes went internal. They’re still the response contract underneath, but you never pick one; recognition does, and shows you its choice as a chip you can flip. In exchange for taking the machinery off the screen I had to add something back: receipts, everywhere, because filing that happens invisibly is filing you can’t trust.

That trade (fewer rooms, more evidence) is the single most useful thing I’ve learned building this.

Eleven services, 289K lines, 40% of it tests

Eleven TypeScript/NestJS services behind REST boundaries: recognition and orchestration, the wiki and its compilation queue, vector search (pgvector), a stateless encryption service, billing, audit, with a Next.js front-end. Streaming is SSE end to end; LLM output is enforced as structured data through tool-use and Zod schemas, never “please return JSON”; every prompt has a golden eval suite with a per-run cost budget. Content is encrypted at rest by default, and CI fails if a change could let content leak into logs or telemetry.

Some numbers, for scale: ~289K lines of TypeScript across 2,171 files (about 40% of it tests), 111 database migrations, 750 test files, 29 end-to-end paths, 31 architecture decision records, and roughly 1,270 commits over nine months, fourteen phases, 178 merged pull requests.

Almost none of which I typed. The agent org that builds it is a project in its own right.

Autonomy is split by reversibility, not by size

  • Prompts are versioned, tested components. Each with a golden eval suite and a dollar cap per run.
  • Encryption by default, with CI checks that fail if content could leave the box.
  • A per-user daily LLM cost cap that degrades gracefully instead of cutting off.
  • A custom Postgres-backed compilation queue (FOR UPDATE SKIP LOCKED, per-entity coalescing, user-edit-wins) instead of reaching for another dependency.
  • Autonomy split by reversibility, not by size: filing is automatic because a misfile is one tap from undone; anything structural (merging two projects, renaming, archiving) can only ever be proposed.

Green CI did not mean it worked

The most useful lesson was a humbling one: for a while, “shipped” didn’t mean “works.” Every service’s own tests passed and CI was green, but none of that exercised the composed system, with the real services talking to each other, real models, and streaming over the network. So a feature could clear every check and still be broken end to end. The fix was as much cultural as technical: trust the running stack, not the mocks.

The sharpest recent one came from the models rather than the code. A prompt that had run fine for weeks started coming back refused: not a bad answer, a categorical decline of the whole task class, after a routine model change. Prompts aren’t code: they don’t break loudly, they don’t break deterministically, and the fix (a reframe, plus a fallback to a second model when a refusal is detected) is a thing you only build once you’ve been bitten.

The rest of them (the database default that looked empty but wasn’t, the tests that were green for the wrong reason, the cost check that turned out not to be an entitlement check) are collected, honestly, on what broke.

Live, paid, used every morning

It’s live, it’s paid, and I use it every day. Onboarding is three screens, the two tabs are real, search covers everything, billing runs, and the invisible half (backups with a rehearsed restore, staging, monitoring, GDPR mechanics) has been done and verified rather than promised.

What I’m working on now isn’t more surface. It’s the gap between a system that files your life correctly and one you actually open in the morning, which turns out to be a much harder problem than the filing was. Try it and tell me where it loses you.

Go deeper

Fittingly for a product that compiles wikis, the details have a small wiki of their own: