Skip to main content
Back to all posts
Oct. 20267 min read

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.

HacktoberfestDEV ChallengeGemma 4EmbeddingGemmaOllamaGmail APIGoogle CalendarLocal AIHackathon
I wrote this post for the DEV Hacktoberfest Open-Source AI Challenge, Week 3: Slow and Steady (October 2026), and built Spare Thumbs for the same submission. It runs on my own laptop against my own inbox, so there is no hosted version to click; the demo video shows the first live run. #hf26challenge
The claim in one sentence: on ten reschedule requests against a busy week, Gemma 4 without my calendar offered a time I was already booked in 7 drafts out of 10, and Spare Thumbs in 0, graded by the calendar itself, where a time either overlaps an event or it does not. The ten-request week is made up so the test repeats; the demo runs on my real calendar, and whether it sounds like me is judged by a model, not by people who know me.
Title card on a dark background: Spare Thumbs in large type, then "Clients move meetings at night. Gemma 4 answers in my words, from my real calendar, and never offers a time I'm already booked." and, in orange, "Gemma 4 + EmbeddingGemma, running locally on Ollama".
Spare Thumbs: Gemma 4 on my laptop answers "can we move the meeting?" in my words, from my real Google Calendar.

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.

Terminal log of a live run: Sam asks to move the website review to Friday 10h; Gemma calls find_meetings, then check_calendar for Friday, which shows 9:30 to 11:00 busy and 11:00 to 18:00 free, then draft_reply offering Friday 11:00 and 14:00, saved as a draft in the Gmail thread.
Live run: Gemma 4 finds the meeting, checks Friday, sees 10h is booked, and offers 11h or 14h, which are actually free.

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.

Gmail thread titled Website review Thursday: Sam's message asking to move to Friday 10h, and below it an unsent reply draft reading "hey sam, sorry about that! 10h on friday is booked up for me. how about 11h or 14h instead? let me know if either works! thx", with the Send button.
The reply lands as a draft in my Gmail thread, written the way I write. Nothing reaches the client until I press send.

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].
EmbeddingGemma pulls my own replies to the most similar messages, including one I sent through it earlier. No fine-tune.
Terminal log: Sam writes "11h works, see you Friday!"; Gemma calls move_event, moved to Friday 30 October 11:00, then draft_reply with "hey sam, awesome! see you friday at 11h. thx!", saved as a draft and moved in Google Calendar from Thursday 14:00 to Friday 11:00.
Sam says 11h works. Gemma 4 calls move_event on its own, then drafts the confirmation.
Google Calendar day view for Friday 30 October 2026, showing the Website review event from 11am to 12pm.
My real Google Calendar after the run: the website review now sits on Friday at 11h, moved there by Gemma 4.

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.

Terminal log: Gemma's first draft offers 9h or 11h on Friday, and a red line reads "BLOCKED: overlaps 'Return sink' 09:30-10:30 on Fri 30 Oct (sent back to Gemma to try again)".
The gate: Gemma offered 9h, the code saw it overlaps a real 9:30 event, blocked the draft and sent it back to retry.

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.

Results card titled Ten reschedule requests, one busy week: a row of ten boxes for Gemma 4 without my calendar with seven red crosses, labelled 7 of 10 drafts offered a time I was booked, and a row of ten green checks for Spare Thumbs, labelled 0 of 10.
Ten requests on a busy week: Gemma 4 without my calendar offered a booked time in 7 drafts. Spare Thumbs: 0.

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.

Related project

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