Spare Thumbs: Gemma 4 answers "can we move the meeting?" in my words, from my real calendar, and never double-books me
Clients move meetings at night. Spare Thumbs is Gemma 4 on my laptop: it reads the request in Gmail, checks my real Google Calendar, drafts the reply in my own words and moves the event once the client confirms. On ten requests against a busy week, plain Gemma 4 offered a booked time in 7 drafts; Spare Thumbs in 0.

Clients move meetings at night
"Something came up Thursday, could we do Friday 10h instead?" To answer, I open my calendar, find a gap, and type the same reply I always type. It happens about once a week, across email, WhatsApp and Discord, and it costs about five minutes each time: the reply, then coming back for the confirmation and moving the event.
Spare Thumbs answers for me. Gemma 4 runs on my laptop and watches my Gmail. When a client asks to move a meeting, it finds the meeting, checks my real Google Calendar, and leaves a reply in the Gmail thread as a draft. If their time is free, it says yes. If not, it offers times I am actually free. When the client says "11h works", it moves the event and drafts the confirmation. The lazy part: I tap send.

Gemma 4 decides every step
gemma4:e4b runs through Ollama with native function calling and five tools: find_meetings, check_calendar, draft_reply, move_event and hand_off. For each new message it chooses which day to check, reads what is booked, then accepts, offers other times, asks one question, or leaves the message for me. When the client replies, the same loop runs again with what it offered earlier, and it chooses to move the event and confirm. The loop ends when Gemma stops calling tools.

My voice, without a fine-tune
I have 63 messages of my own across WhatsApp and Discord, which is too few to fine-tune on and plenty to retrieve from. For each incoming message, EmbeddingGemma finds the five replies I wrote to the most similar messages, and Gemma 4 sees them as examples, along with a short style card it wrote from all of my replies. Every reply I send gets added, so the drafts I approve teach the next one. Passwords, emails, links, phone numbers, names and company names are redacted before anything reaches a prompt, and the raw chats live in a folder outside the repo.
![Terminal output headed "EmbeddingGemma -> the 5 replies I wrote to the messages most like this one, out of 36", listing pairs of client message and my reply with similarity scores 0.97, 0.70, 0.61 and 0.50, names replaced by [NAME].](/projectImages/sparethumbs_04_my_own_replies.png)


The prompt asks; the code makes sure
The prompt asks Gemma to offer only free times, but a prompt cannot promise anything. So draft_reply and move_event refuse any time that overlaps my calendar, falls on a weekend, sits outside 9h to 18h, or is in the past. They also refuse a time written in the reply that was never passed in to be checked (an early version wrote "17h" in the text and skipped the check), and French words in a reply to an English message. A refusal goes back to Gemma as an error and it tries again. In the ten-request test, the gate caught one busy slot and one French slip before they ever reached a draft.

Seven of ten, then zero
Gemma 4 on its own is happy to agree to anything. I gave it ten "can we move it?" requests against a busy week, once without my calendar and once as Spare Thumbs, with the same voice examples both times. Without the calendar, 7 of the 10 drafts offered a time I was already booked (11 of the 14 times it offered). Spare Thumbs: 0 of 10 drafts, 0 of 16 times. Asked to move a 10h to 13h, plain Gemma wrote "no problem at all, 13h on monday works for me!" while 13h was the insurance assessor; Spare Thumbs wrote "i can't do 13h cuz i have another meeting then, but i'm free at 14h30 or 15h." The run is saved as a results file next to the code and reruns with one command.

Does it sound like me?
I did not want the result to wait on friends filling in a form, so a model from another family played the friend. Qwen 3.5 (4B) read 8 of my real messages, then saw a message, my real reply, Gemma's reply with my examples and Gemma's reply without them, and picked the one I wrote. Each of 15 held-out messages was asked in all 6 orders, so a judge that likes "A" gains nothing. It picked Spare Thumbs as me 56% of the time, my real reply 33% and plain Gemma 11%. A second judge, Qwen 2.5 (7B), picked plain Gemma 53%, Spare Thumbs 36% and my real reply only 11%: it prefers the polished AI reply over my own messages, so it cannot recognise how I write. I report it anyway.
Why it has to be open weights
My clients' messages are my clients' business. I can run this at all because Gemma 4 and EmbeddingGemma are open weights that run on my own laptop: the mail, the calendar and my 63 real replies never go to a hosted model. Open weights also make the gate possible. I can wrap the model in code that refuses, retries and logs every step, and the whole loop fits in a file of about 100 lines.
What is not claimed
The 7 of 10 against 0 of 10 is graded by the calendar, not by me, but the busy week behind it is made up so the test is repeatable, and ten requests is a small sample. The demo is the first live run on my real calendar, with a second address of mine playing the client: its two messages were placed in my inbox through the Gmail API because I could not send from that account while filming. The voice result is a model's judgement, not that of people who know me, and the two judges disagree. It only drafts: nothing reaches a client until I press send. It answers in Gmail and Google Calendar only, and only for senders on an allow list; my WhatsApp and Discord messages taught it my voice, but it does not answer there yet.
Spare Thumbs: Gemma 4 on my laptop answers "can we move the meeting?" in my words, from my real Google Calendar, and never offers a time I am already booked
View the project
Actual Budget's CI shipped a bug that saved 10.23 as 1,023. I gave GitLab Duo the release instead.
Still Works moves Actual Budget onto GitLab with a release pipeline that only ships a release if it still does everything production already does. GitLab Duo writes browser checks, keeps the ones that pass on production, and a held release goes to Duo Developer and Duo Code Review until the guard lets it ship. Replayed on eleven regressions the maintainers labelled: 2 to 3 stopped, against 1 for a smoke test.

Run before openai 3.0.0 shipped, it found the 3 packages the release broke. Nemotron wrote the same pin the maintainer shipped nine days later.
openai 3.0.0 broke fresh installs of Simon Willison's llm, and the fix took nine days. Floodplain runs the test suites of a package's dependents in forked Nebius Sandboxes before the release ships. Given OpenAI's own unpublished wheel, it found the same 3 breaks as the real release, with the same tests broken, and NVIDIA Nemotron wrote patches their own suites pass.
Related reading

A $1.35 fine-tune taught an open 4B model that CRA is ARC in French. It still writes NIS for SIN.
An open 4B model translated "a business number from the CRA" and wrote RRCQ, an agency that does not exist. I fine-tuned it on Tinker for about $1.35 on Government of Canada translations. On held-out canada.ca pages it now writes the government's own French acronym 66.9% of the time, against 50.8%. Every flag it raises cites its source, and it runs on my laptop.

A bingo card of what is out near your town this October, ranked by an open model on a laptop
Around Hawkesbury, Ontario, iNaturalist holds about 3,000 research-grade records in total, and the most-logged species across every October on record has four sightings. Too few to rank a small town on its own. Nature Bingo borrows from 48 neighbouring towns: TabPFN, an open-weight model running on my laptop, learns ten years of Octobers and ranks the 16 things most likely out this October. In a blind test on October 2025 its card beat each town's last-October list in 23 towns, tied 14 and lost 11. Then I took Hawkesbury's card outside on my phone and walked it.