Off the clock · Feb 2026
Amble, a todo app I talk to
I wanted to ramble my todos instead of typing them, without anything changing until I'd seen it. It started as one voice command at a time and got to rambling with a review step in seven weeks.
Why am I typing my todos when I could just say them?
Adding a todo in most apps means opening it, picking a list, typing a title, then setting a date and priority by hand. Each step is small, but together they add up, and they get worse when I have five things in my head at once. What I wanted was to hold one button and say "finished the tax forms, add buy milk, move the dentist to next Monday, and break down the Japan trip", and have all four things happen. Not one command per recording, a ramble.
TODO(tarang): one or two lines on what actually set this off.
The app is called Amble (it was VOKL for the first month). It's a Flutter app for Android and iOS with a FastAPI and Postgres backend, and I built it between February and March 2026 with Claude Code, in 226 commits over seven weeks. Today I hold a button, a home-screen widget or a Quick Settings tile and talk. Sarvam AI streams my speech back as English text, which I can fix, Gemini turns that text into a list of proposed changes to my tasks, and I approve each change as a card. Nothing changes until I submit, and then the whole batch lands together or not at all.
How it works today
flowchart TD
A["Hold the button, widget or tile"] --> B["Speech streamed to the server"]
B --> C["Sarvam: speech to English text"]
C --> D["Editable transcript"]
K["Or just type"] --> D
D --> E["Gemini: what changes were asked for?"]
E --> F["Gemini again: where does each new task go?"]
E --> G["Database finds the tasks meant"]
F --> H["A card for every proposed change"]
G --> H
H --> I["Approve, edit or drop cards"]
I --> J[("All saved together, or nothing")]
L["Taps and swipes"] --> M[("Copy on the phone, syncs later")]
M --> J
I wanted two places where I check the machine before it touches my tasks. After transcription I can fix the text before any AI reads it, and after that I see every proposed change as a card. I gave Sarvam exactly one job, turning speech into English. Its translate mode lets me speak Hindi, English or a mix of both and get English back in one go, so everything after it only ever deals with an English sentence.
Gemini (2.5 Flash Lite) then reads that sentence alongside my whole task list and returns the actions: add, complete, edit, delete or break down. Most of its instructions are rules I added each time it got something wrong in testing. "Finished X" is a complete, "X isn't important anymore" lowers the priority rather than deleting it, "no wait, I mean…" keeps only the correction, and when it isn't sure whether something is a task, it treats it as one. My thinking there was that losing something I said is worse than an extra card I have to swipe away. Every task also goes into one of eight areas of life (career, finance, health, relationships, growth, recreation, home and community), and I wrote a definition for each plus tie-breakers for the pairs the model kept mixing up, so travel is recreation even with family and a team lunch is career, not relationships.
Each new task then gets a second, much narrower Gemini call that only sees its own area and answers one question: does this belong inside an existing group, or at the top level? I told it that wrong nesting is worse than no nesting, because a task at the top is easy to move and one buried in the wrong group is easy to lose, and to think of it like filing paper into folders.
I also decided Gemini never gets to pick which existing task I meant. It describes it ("the dentist thing"), and the database finds the closest match by text similarity, shows it on the card with a match score, and keeps a few alternates in case it guessed wrong. "Break down the Japan trip" comes back as four to six new subtask cards under that task.
On the review screen I can untick cards, edit anything, change an action, pick a different parent, record more, or go back to the transcript. Submit saves the whole batch in one go, and if any part fails, none of it is saved, so I never have to clean up a half-applied ramble. Ordinary taps (ticking things off, archiving) work offline from an encrypted copy on the phone and sync later. I didn't bother making voice work offline, since it needs Sarvam and Gemini anyway.
How I got here
The shape changed a lot in seven weeks. I swapped the speech engine three times, and the step where I approve changes arrived in the middle and then took over everything.
1. An app that did everything itself
I started with the simplest thing I could picture: a complete Flutter app in one go, with no server at all. I recorded a voice note, Gemini transcribed it, and a second Gemini call looked at my tasks and suggested where the new one should go, under one of two tabs, Work or Personal, which I could accept or change. It could only add at first, but by the afternoon Gemini could also detect whether I wanted to complete, delete, edit or ask about a task, with a confirmation for anything destructive.
That evening I set up a FastAPI backend, and four minutes later I deleted the whole Flutter app. The commit message just says "starting from scratch".
TODO(tarang): why delete the first app the same day, and why move the AI to a server at all?
2. A backend, one command at a time
I built the backend around one idea: I say one thing, Gemini cleans up the text and picks a single action (add, complete, delete, edit, move or ask), a fuzzy matcher finds the task I meant, and the server does it straight away. If it wasn't sure which task, it asked me to pick. The new app, still called VOKL, arrived the next day, and within the week voice moved to the phone's own speech recognition. I spent the rest of that week on the app itself and then didn't touch it for two weeks.
It worked, for one thing at a time, and that was exactly the problem, because that isn't how todos come out of my head. When I came back to it, I wrote down the two things I wanted to change: one command per recording, and acting immediately.
3. Rambling, and approving before anything changes
The day I came back, before changing anything, I wanted a way to tell whether a change made things better or worse, so I started by building a test set for the one-command prompt: 65 cases against a fixed list of 167 tasks, with a baseline of 89%. Then I planned "Ramble mode" and started building it, and the plan is very close to what the app is now: audio streamed to the server instead of recognised on the phone, many tasks from one recording, and changes shown as cards I review and submit instead of being carried out on the spot.
So the approval step arrived together with rambling. Once a single recording could make five changes, I realised I could let the model be more generous about what counts as a task, because I'd see every proposal before it was saved and catch the mistakes on the screen.
TODO(tarang): why review-before-submit, in your words (a wrong delete earlier, or five changes at once felt too many to run blind?).
For the speech I used Gemini Live, which is built for conversation, as a listener: the instruction was literally "You are a silent listener. Do not respond." It gave back text in whatever script it heard, so a second Gemini call converted anything non-English into English script at the end. The recording screen also got an editing step, which is where the "fix the transcript first" checkpoint comes from. I had wanted cards to pop up while I was still talking, but that needed a much bigger rework than it was worth, so I dropped it and Live stayed a transcriber.
Within a few days rambling could complete, edit, move, delete and break down tasks, and it had its own test set. At that point the app had two paths side by side: the old one for a typed command that ran at once, and the new one for rambling.
4. One path, and eight areas of life
Three things changed in one day in early March. First, finding "the dentist thing" moved into the database. I wanted to split the work cleanly: the model understands what I want and describes the task, and Postgres works out which row it is by text similarity. I also tried sending Gemini a compressed version of the task list to save tokens, but the results got worse, so I took that back out a few hours later.
Second, I deleted the one-command path, about 1,250 lines of backend, because rambling already did everything it did and keeping two paths in step wasn't worth it. Typing now goes through the same review screen, so since then there's no way for the AI to change my tasks without showing me a card first.
Third, the five categories (work, personal, health, learning, projects) became the eight areas the app uses today, each with its own colour.
TODO(tarang): why these 8 areas, and why drop Work/Personal.
5. Sarvam for speech, then a week of hardening
Just after midnight the next day I swapped Gemini Live for Sarvam. Sarvam's translate mode gives English text straight from mixed Hindi and English speech, so the separate conversion step went away, and since then Gemini never hears me, it only reads text. This is where keeping listening and understanding separate paid off: the swap didn't touch the part that works out the actions at all.
TODO(tarang): the real reason for Sarvam beyond dropping the conversion step (speed, cost, accuracy on Hinglish?).
The rest of that week was about making it solid. The test set grew from 64 to 137 cases and turned up 18 failures. Five of those were my test data being wrong, and the real ones became prompt rules, which took it from 87% to 95%. VOKL was renamed Amble and the neon colours became earthy ones. Real logins with Firebase replaced passing a user ID around, submitting a batch became all or nothing instead of saving one change at a time, and ordinary taps became offline-first, with an encrypted copy on the phone and a queue that syncs when the phone is back online.
TODO(tarang): why the rename and the calmer palette.
6. A separate step for where tasks go
The 137 cases were mine, written against my own tasks, and I started to doubt that scoring well on my own examples meant much. So I built a harder set from real-world todos in a public dataset plus voice-style and hand-written cases, 270 in all, against a list of 216 tasks. It was very clear about the weak spot. The app got the action right 95% of the time and the area right 85% of the time, but put a new task in the right group only 40% of the time. With one big prompt doing everything, it nested things wrongly most of the time.
So placement became its own step. The first call only works out the actions and areas, and then each new task gets its own small call, run in parallel, that sees just its area's tasks. That took placement from 40% to 58% for about a second of extra wait, an 18 point gain on the weakest number. Most of what was left was over-nesting, so the filing-folders test and examples of "no parent" went into the prompt, and placement reached 64%.
The next day I restructured the first prompt around the bias towards adding, made the written area definitions the authority, and set the temperature to zero. The test set grew to 553 cases, and on the 270 it now caught every task and got the area right 95% of the time. A few rounds also went into fixing wrong labels in the test data rather than the prompt.
7. Ramble from anywhere
The last stretch was mostly the app around all of this: a UI overhaul, voice-first onboarding, a connectivity check that actually asks the server instead of trusting the phone's network status, and archiving. I had listed widgets and shortcuts as a non-goal back in February, but by now opening the app felt like the slowest part of a ramble, so I added three on Android: a home-screen widget, a long-press app shortcut and a Quick Settings tile, all opening straight into recording. In April I cleaned out paths and passwords and made the repo public.
What I learned
Looking back, the two decisions I'm happiest with are approving before anything changes and keeping listening separate from understanding. The first costs me a tap, but a misheard word can't quietly delete something. The second is why swapping the speech engine didn't touch the rest of the app. The other big calls, letting the model describe a task while the database finds it, and splitting one wide prompt into two narrow ones, both came out of the test sets, and I didn't let a prompt change in March go in without checking it against them.
There are two gaps in what I built. The save step doesn't check that the tasks it changes belong to the person saving, which is fine for me alone and not for anyone else. And I contradicted myself: the prompt asks for uncertain items as low-confidence adds so nothing I say gets lost, and then the server quietly drops everything below a confidence bar that those very items sit under.