Inspiration

Everyone has a plan. Almost nobody has the hour.

The gap is never intention, it is time. You decide the thesis matters, and then Tuesday happens. Most tools solve the first half: they hold the list. Nothing holds the hour, and nothing is still working on Thursday when the plan you made on Monday has quietly stopped being true.

We wanted a partner that keeps working after the conversation ends.

What it does

Blink turns a goal into real sessions on your real Google Calendar, then stays awake between conversations. It reminds you with the reason you gave it, times the work so the number is measured rather than remembered, replans the week when life breaks it, and asks you at the end of the day how it actually went. You can talk to it: tap the mic and it listens, answers in its own voice, and keeps the conversation going hands free.

How we built it

The split that everything else follows. A deterministic core does the arithmetic: capacity, placement, conflict detection, streaks, pacing. No model sits in that loop, because those are things that have to be right rather than plausible. Gemini decides what to do. The code decides what is true.

Models. gemini-3.5-flash is the workhorse and the model behind the ADK agent itself. gemini-3.5-flash-lite handles the cheap high-frequency step, intent classification, where the job is picking a label rather than reasoning. gemini-3.1-pro-preview is wired but held in reserve for a single deep reasoning step. All of it runs on Vertex AI with a keyless service account.

Temperature stays at 1.0, on purpose. Gemini 3.x is tuned for its default, and dropping to 0 degrades it. Where we need reproducibility we get it the correct way: a response_schema the reply must conform to, plus a fixed seed. Structure comes from the schema, not from starving the sampler.

Thinking is budgeted per call site. Gemini 3.x defaults to high thinking, which is slow and a real cost hazard because thinking tokens are charged against the output budget. So every call names a tier. Steps that only follow instructions (pick an enum, pull fields out of text, rephrase settled facts) run at minimal. Steps that exercise judgment (which question to ask next, how to shape a plan from nothing) keep a small real budget. One model in the family rejects the minimal level outright, so that downgrade is applied before the request is built rather than discovered as a failed turn.

The agent is a real ADK agent on the live request path. LlmAgent with 28 typed tools, selected from their docstrings by function calling. There is no keyword routing anywhere: the model reads what a tool does and decides. Tools return real values, and replies are composed from those values rather than from the model's account of them.

The confirm gate is structural, not a prompt. Anything that writes to your Google Calendar exists as a pair: a propose_* tool the model can call, and a *_confirmed tool it cannot. The confirmed half is not in the model's toolset at all, and a before_tool_callback blocks it a second time. Only the endpoint behind your yes can execute it. It is impossible by construction rather than by instruction.

Web search is Gemini's own Google Search grounding, permission-gated on first use, with the returned text treated as untrusted data: URLs never reach the prose and never reach the voice.

The voice is Cloud Text-to-Speech, en-US-Chirp3-HD-Charon, a calm Chirp 3 HD voice. Whole replies synthesize to MP3; the low-latency path streams headerless LINEAR16 and plays the first chunk while the rest arrives.

Every model path has a deterministic fallback. If Gemini is unavailable the product degrades to grounded templates that state what the store actually holds. It never fabricates, and it never claims an action it did not take.

The rest of the stack: FastAPI on Cloud Run, Firestore for plan and history with dirty-section flushes kept off the request path, Google Calendar over OAuth, and a SwiftUI companion that shares the same API, with on-device speech recognition and APNs for the signals that arrive while the app is closed.

Verification. 849 offline tests with the model mocked at a single seam, so the whole suite runs without spending a token. Plus a live probe that runs real requests through the real model and scores which tools it actually chose, with destructive mistakes counted on their own axis.

The data loop

The track is judged on data, so we built the loop deliberately. Blink ingests typed and spoken turns, your calendar, and the minutes its own timer measures. It keeps that history as a lifecycle rather than a transcript: the conversation log is capped, derived numbers are computed at read time, and measured minutes are recorded fact. It filters, because an insight has to occur at least three times before it is allowed to speak, and one you dismiss never returns. It shapes its responses from what it learned, keeping the hours you told it to leave alone and saying so. And it self improves: an accepted insight becomes a constraint on how it plans you from then on.

Challenges we faced

It lied to us once. Early on it said it had removed a calendar event. It had not. The chat path had no tools at all, so the model simply narrated a plausible action. That bug set the rule the rest of the project follows: never claim an action not taken. Replies are now composed from what tools actually returned.

Measured is not the same as remembered. If the timer measured ninety minutes, that is a fact. If you told us ninety, that is your word, and it is worth recording but it is not the same thing. They are never added into one number anywhere in the system, and a test asserts it.

We audited ourselves and did not like the answer. We wrote 100 real requests a user might make and scored the agent against them honestly. It came back 42 out of 100. The write tools were fine, but nothing could feed them: no tool could list the sessions a bulk request referred to. We fixed the selection layer and re-scored to 54, and the audit is in the repo with both numbers, because the before is the useful half.

What we learned

Honesty is not a feature, it is architecture. Every safeguard we are proud of is a structural one: a tool removed from a toolset, a number that cannot be summed, a listing that refuses to omit. Politeness in a prompt is not a guarantee.

What's next

Bringing the same loop to the watch, and letting Blink prepare the work and not only the hour.

Built With

Share this project:

Updates

Submission history