This is a submission for Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady.
What I Built
Pomodoro timers have a dirty secret: the 25 minutes isn't the hard part. Deciding what to do with them is. You hit Start, stare at "my history essay," and burn 10 minutes reorganizing your desktop.
So I built Tuck: a tiny 3D acorn in a leaf hat who sits next to your timer and does the deciding for you.
- Type what you're working on ("my history essay") and hit Start.
- Tuck, running on open-weight gpt-oss-20b, shrinks it to one goal that fits the session: "Draft the intro paragraph. Just that."
- He sits down and works quietly next to you. No notifications. No streaks. No guilt.
- When time's up he asks "Did you finish?" Tap Yes, Almost or Not yet. He hops, nods or tilts his head kindly, then hands you the next small step.
Stuck mid-session? Ask Tuck has four one-tap buttons: Plan this session, I'm stuck, I'm distracted and Quiz me. Replies are a sentence or two, on purpose. A focus buddy that writes you an essay is just another tab.
Demo
Live: https://pocket-pal-teal.vercel.app
Type a task and hit Start. Don't want to wait 25 minutes for the check-in? Set Focus to 1 minute in Settings.
Every screenshot here is the live app with real gpt-oss replies.
2. Tuck turns them into one goal, then sits down and works with you.

3. Time's up, he checks in. No verdict until you answer.

5. Your next step is already waiting, with zero extra AI calls.

Code
UribeJr / pocket-pal
Pocket Pal: a tiny 3D desk companion PWA (Phase 1)
Meet Tuck: your focus partner
Tuck is a calm Pomodoro timer with a focus partner: a tiny 3D acorn who keeps you company. Tell him what you're working on and he turns it into one small goal that fits your session. When the timer ends he asks "Did you finish?" and reacts: a hop for a win, a nod for "almost", a kind head tilt for "not yet". Then he suggests the next step.
Live: https://pocket-pal-teal.vercel.app
How it works
- Start with an intention. Type "my history essay" and press Start. The timer starts at once, and Tuck replies with a goal like Draft the intro paragraph. It's sized to your real focus length (25 minutes by default, or whatever you set).
- Focus. Tuck sits and works quietly beside you. If you get stuck, open Ask Tuck and tap I'm stuck, I'm distracted or Plan this session. Each…
How I Built It
Tuck is a real 3D character, not a sticker. I modelled and rigged him in Blender, exported a GLB, and render him with React Three Fiber on Next.js 15, React 19 and TypeScript.
- He lives in a soft "infinity cove" studio. The camera uses a view offset so the room flows behind the timer as one space.
- A small emotion system drives his faces, blink rate, and an allowlist of clips: hop, stretch, sit, wave.
The timer is pure timestamps. Reload, background the tab, close your laptop for three hours. It never loses or double-counts a session.
The AI loop is tiny on purpose. About two calls to openai/gpt-oss-20b on Groq per Pomodoro: one at Start (the goal), one at the check-in (reaction plus next goal).
- Replies follow a strict JSON schema:
line,goal,emotion,action. - The server validates them with zod. Nothing the model writes ever becomes HTML.
The model suggests. The app decides the body. Whether Tuck hops, nods or tilts at check-in comes from your answer, never the model. The model only gets to pick from a short list of safe moods.
Guardrails live in the prompt:
- Quiz me asks one question and grades your answer in your own words.
- "Write my essay" becomes "let's outline it together."
- Stress gets a warm acknowledgement before any advice.
- Time questions are answered from the real timer ("You've been focusing for 12 minutes").
The bug that gaslit me
After two good replies, every message said "Ask Tuck isn't connected. It needs its AI key." The key was right there.
Production logs said otherwise: Groq was returning 400 tool_use_failed. gpt-oss sometimes decides to call a tool, even when you define zero tools. My error handling lumped every weird 400 into "misconfigured," and the UI translated that to "no key." So the app was confidently lying to me about the wrong problem.
The fix:
- Treat it as a bad sample. Resample once, cooler, as a forced call to a single
tuck_replyfunction. The model wants to call a tool? Fine. It gets exactly one. - Fail fast on rate limits instead of retrying into a wall.
- Fall back to
gpt-oss-120b, which has its own rate-limit bucket.
Every error now shows an in-character line, like "Phew, I need a quick breather. One minute?", with a Try again button.
Building for a free tier
Groq's free tier for gpt-oss-20b is 8,000 tokens a minute, shared by everyone using the app. That constraint designed half the product:
- Short goals, not long tutoring answers.
- A slim prompt of about 900 tokens.
- The goal Tuck suggests at check-in becomes your next Start, no extra call.
- No AI where none is needed. Timer lines and reactions stay local.
Why Does Open Innovation Matter?
- I'm not locked in. gpt-oss-20b is open-weight under Apache 2.0. Today Groq serves it fast and free. The same code points at any OpenAI-compatible server with one env var,
AI_BASE_URL, including Ollama or LM Studio on your own laptop. (Haven't tested a local server yet.) For a focus app, that means your tasks could stay on your machine. - Swapping models is config, not a rewrite. Falling back from 20b to 120b took zero code changes.
- Open weights come with open docs. When the model did something weird, like reaching for a tool that didn't exist, I could read how it formats responses and design around it instead of guessing at a black box.
- Where open beat closed for me: cost and control. You don't need the biggest closed model on earth to say "Draft the intro paragraph. Just that."
The Lazy Payoff
You type four words. Tuck picks the one thing worth doing in the next 25 minutes, sits with you while you do it, and checks in after. Slow and steady: one small goal at a time, no to-do list required.
Go say hi: https://pocket-pal-teal.vercel.app 🌰









