DEV Community

Arsh Goyal
Arsh Goyal

Posted on

Still Here: I built my friend a heartbeat, so silence stops being scary

Hacktoberfest Weekend Challenge: Build for a Friend Submission ๐Ÿค

This is a submission for the Hacktoberfest Weekend Challenge: Build for a Friend

What I Built

My friend Ben lives far away, on their own. We talk, but the way friends who live apart talk: a burst of memes one week, then nothing for four days. Almost always, nothing just means busy. But I never actually know, and I didn't want to become the friend who sends "you alive??" every morning.

What I wanted didn't exist in the shape I needed. Location sharing felt like surveillance. Wellness apps ask for a mood journal nobody keeps up after day three. I wanted something that costs Ben one second a day and takes the guesswork off me.

So I built Still Here: a small check-in companion that lives in Ben's Telegram.

  • Every morning it sends Ben one short message that remembers Ben's life: "how did the interview go?", not "How are you feeling today?"
  • Ben replies with anything: a word, an emoji, a photo of lunch. That's the whole job.
  • If Ben goes quiet, it sends one gentle nudge a few hours later: "no pressure, a ๐Ÿ‘ is plenty."
  • If the silence lasts about 10 hours, it messages me, calmly: "Haven't heard from Ben since this morning. Probably nothing, but a call would be lovely."
  • The moment Ben replies, I get "Ben just checked in ๐Ÿ’›" and we both go back to our day.

The design rule I cared about most: I never see Ben's messages. Each reply becomes a 1โ€“5 mood score, one mood word, and one line written to be shareable. That's all buddies get. Ben has a private page showing exactly what I can see, can /pause for a few days without explaining why, and can /forget everything the bot remembers.

Buddies see Stays with Ben
A mood score, one word, and one shareable line per day Every message Ben sends
An alert after a long silence, and "Ben's back" when it ends What the bot remembers (Ben can delete any item)
A short note about the week, every Sunday /pause, /forget, and a button to remove any buddy

Ben is also told every time someone becomes a buddy. Nobody gets added quietly.

Demo

Live app: https://still-here-9epp.onrender.com

You don't need Telegram or an invite to look around. Both pages below run on the deployed app with a made-up month of data:

The centrepiece is the pulse: Ben's last 30 days drawn as a heartbeat line.

  • A beat is a day Ben checked in, and taller beats are brighter days.
  • A flat stretch is a day Ben went quiet.
  • A shaded block is a pause.

I wanted something I could read in one glance at 11pm and know whether to call.

Here's how the model handled three messages I wrote while testing. These are real outputs from the deployed prompts, but not Ben's real messages:

Message to the bot What the bot replied What buddies saw
"honestly kinda nervous lol, didn't sleep much. my landlord also raised rent by 8k which is stressing me out" "man, that's a lot to deal with all at once. i'm sorry about the rent situation. do you think you'll be able to get some rest today?" 2/5 ยท stressed: "Ben's having a bit of a heavy week."
"it went great!! they want a second round ๐ŸŽ‰" "omg ben that's amazing!! so happy for you. when is the second round?" 5/5 ยท excited: "Ben is doing great and moving to the next interview round!"
"don't tell anyone but i got into a fight with my sister, feeling crap" "oh no, i'm so sorry to hear that. sending you a big hug. do you want to vent about it or just distract yourself?" 1/5 ยท upset: "Ben's having a bit of a rough day."

The rent amount and the sister never reach buddies. That third row is the one I care about.

Code

๐Ÿซ€ Still Here

A daily check-in for the friend who lives far away.

One small Telegram message every morning. Reply with anything and you're done.
Go quiet for too long, and the people who love you get a gentle nudge to call.

Hacktoberfest 2026 Gemma 4 Render

Deploy to Render


๐Ÿ’ก Why I built this

My friend Ben lives far away and on their own. We text, but texting is uneven. Some weeks it's memes every hour, then nothing for four days. Usually nothing just means busy. But I never actually know, and I didn't want to be the friend who sends "you alive??" every morning.

The apps that exist for this are either surveillance (location sharing, read receipts) or wellness apps nobody opens after day three. I wanted something in between:

  • Zero effort for Ben. No app to install, no forms, no streaks to feel guilty about. One message, and any reply counts.
  • Zero anxietyโ€ฆ

It's Python and FastAPI, about 2,700 lines including templates, CSS and tests, with no frontend build step. render.yaml deploys the whole thing.

How I Built It

Architecture

Ben โ‡„ Telegram โ‡„ Render web service (FastAPI)  โ‡„  Render Postgres
                        โ”‚   โ–ฒ
                        โ”‚   โ””โ”€โ”€ POST /heartbeat  โ—€โ”€โ”€ GitHub Actions, every 10 min
                        โ–ผ
                 Gemma 4 (open weights, via Google AI Studio)
Enter fullscreen mode Exit fullscreen mode
Piece Job
Render Web Service Telegram webhook, buddy dashboard, Ben's private page, setup page, and the heartbeat endpoint
Render Postgres Check-ins, moods, memories, events
GitHub Actions (*/10 * * * *) Calls the authenticated POST /heartbeat, which also wakes the free instance
Gemma 4 Writes, understands, summarises and redacts
Telegram Bot API The whole interface. Raw HTTP, no SDK

The heartbeat is a small, boring state machine, on purpose

Each beat converts "now" into Ben's local time and asks four questions:

if local.time() >= checkin_time and not db.checkin_for_day(today):
    send_checkin()                                   # once a day
if ci.status == "waiting" and hours >= nudge_after and 8 <= local.hour < 22:
    nudge()                                          # never during Ben's night
if ci.status in ("waiting", "nudged") and hours >= escalate_after:
    tell_buddies()                                   # once, calmly
if is_sunday_evening and not digest_sent_today:
    send_digest()
Enter fullscreen mode Exit fullscreen mode

Every beat is stateless and idempotent: it only reads Postgres and the clock. If a beat is missed, the next one catches up. If two run at once, the second does nothing. That mattered more than anything else in the codebase, because the worst bug this app can have is telling me Ben has gone quiet when Ben hasn't.

The tests simulate whole days with a frozen clock and a fake Telegram that records every message:

  • a quiet day that nudges, escalates exactly once, and resolves when Ben replies
  • a reply that closes the day with no alerts
  • a pause that suppresses everything
  • a crisis message
  • the Sunday digest sent exactly once
  • forged invite codes, tampered dashboard links, and unauthenticated webhook or heartbeat calls

There are 10 tests and all of them pass.

I originally used a native Render Cron Job for the heartbeat. Then I hit a wall: cron jobs need a paid plan, and I didn't have a card this weekend. So the web service exposes the same tick at POST /heartbeat, protected by a key, and a free GitHub Actions schedule calls it. The cron job is still in render.yaml as a commented-out block for anyone who prefers it.

Render made the rest easy:

  • One Blueprint creates the web service, the database, and generated secrets.
  • The service registers its own Telegram webhook on boot using the RENDER_EXTERNAL_URL that Render injects, so deploying is: paste two keys, click once.

Gemma 4 has four bounded jobs

Job Input Output
Write the check-in Up to 30 short memories, the last few mood words, Ben's local time One short message
Understand a reply The message (or a photo; Gemma 4 is multimodal), recent chat, memories JSON (via JSON mode): reply, mood_score, mood_word, share_line, memory, concern
Nudge This morning's check-in One guilt-free follow-up
Weekly digest Only the week's shareable lines and scores, never raw messages A short note for buddies

The share_line prompt has explicit rules:

  • no other people's names
  • no health or money details
  • nothing Ben asked to keep private
  • when in doubt, "Checked in, sounds okay."

The digest is built only from those lines, so even a bad summary can't leak a message it never saw.

What went wrong, and what I changed

  • Thinking tokens. Gemma 4 returns its reasoning as response parts flagged thought. I filter those out, and setting thinkingLevel: "minimal" cut a simple JSON call from ~7.2s to ~1.4s in my tests.
  • The hosted free tier is flaky. During one test run I got HTTP 500s, 503 "high demand" errors, and a digest that took 88 seconds. Every call now alternates between gemma-4-26b-a4b-it (a fast MoE) and gemma-4-31b-it, with backoff and a 30-second timeout. Every job also has a hand-written fallback. A model outage should never mean Ben gets silence.
  • The model guessed a pronoun. An early digest called Ben "he". I don't know Ben's pronouns from a name, and neither does a model. Every prompt now says to use Ben's name or "they".
  • Safety can't depend on a model's mood. Gemma returns a concern level, but a plain regex also runs on every message. If it matches crisis language, Ben immediately gets helpline information and buddies are alerted, whatever the model said.
  • A double message on day one. If Ben joined in the afternoon, the next beat would send a "morning" check-in right after the welcome. Now the welcome counts as the day's check-in. A test covers it.

Current limits

I'd rather list these than have a judge find them:

  • Inference isn't local yet. Ben's messages currently go to Gemma through Google AI Studio's hosted API. Open weights make moving it to my own hardware a one-file change, but I haven't done that yet.
  • The free Render instance sleeps. The first message after it goes idle can take about a minute to answer. Telegram retries, so nothing is lost, and the heartbeat wakes it every 10 minutes anyway.
  • GitHub's schedule can run late, sometimes by 10โ€“20 minutes. That's fine for a 10-hour alert window, but not for anything time-critical.
  • The crisis check is English-only and keyword-based. It's a floor, not a clinician.
  • Free Postgres on Render expires after 30 days. Keeping Ben's history means upgrading the database plan.
  • One friend per deployment, and text and photos only. Voice notes are acknowledged but not transcribed.
  • It's tested on simulated days and my own messages. Real use with Ben is what comes next.

Still Here is not an emergency service, and the app says so on every page.

Why Does Open Innovation Matter?

Still Here handles something more sensitive than most hackathon data: a running record of how one person is feeling, every day. That changed what I was willing to build on.

  • I can choose where inference happens. Gemma 4 is open-weight. Today Google AI Studio serves it free. If that changes, or if Ben would rather nothing leave our own infrastructure, I point stillhere/gemma.py at vLLM or Ollama on a GPU instance and nothing else changes. With a closed API, the provider decides where those words live, permanently.
  • One instance per friend. There's no Still Here company holding a database of people's bad days. Each person deploys their own copy, and the data sits in a Postgres they own.
  • The rules are readable. The exact moment I get an alert about Ben (escalate_after_h = 10) is a number in a file Ben can open, along with every prompt and the crisis check. That kind of trust is hard to offer from a black-box app.
  • I could actually test the model. With open weights in two sizes I could compare them on the same prompts, use the faster one by default, and fall back to the larger one, instead of being stuck with whatever a single closed endpoint does that day.
  • It costs nothing to run: a free Render web service and Postgres, a free GitHub Actions heartbeat, and the free Gemma tier. A safety net for someone you love shouldn't need a subscription.

My Agent Session

I built Still Here with an AI coding agent in my terminal. The session covered probing which Gemma 4 models my key could reach and how they behaved (thinking parts, JSON mode, latency), writing the heartbeat state machine test-first with a frozen clock, checking the UI in light and dark mode at phone width, and moving the heartbeat from a paid Render Cron Job to a free GitHub Actions trigger when I didn't have a card.

Handing It to Ben

Still Here is live, and Ben's invite link is a single tap: open it, press Start, and the first message arrives. I'll update this section with Ben's first reply and whatever Ben asks me to change, because that feedback decides what I build next.

What's Next

  • Voice notes, transcribed by an open speech model, for days when typing is too much
  • Gemma on a small GPU instance I control, so not a single word leaves our own infrastructure
  • A buddy rota by time zone, so alerts go to whoever is awake rather than everyone at 3am
  • Spotting a slow slide: a two-week drift in mood, not just one bad day, with a gentle suggestion to call before it becomes a crisis
  • Testing with Ben for a few weeks, and changing whatever Ben finds annoying first

Prize Categories

  • Best Use of Render. Render runs the whole agent: a Web Service and Postgres from a single free Blueprint, a Telegram webhook that registers itself from RENDER_EXTERNAL_URL, and an authenticated /heartbeat that wakes the free instance and runs the check-in state machine. A native Render Cron Job is included as an option.
  • Best Use of Gemma. Gemma 4 (gemma-4-26b-a4b-it, falling back to gemma-4-31b-it) writes the daily check-ins, understands replies and photos, produces structured JSON mood data, and redacts each reply into a shareable line that buddies can safely see.
  • Best Use of GitHub Copilot. A GitHub Actions schedule is the heartbeat: every 10 minutes it wakes the Render service and runs the tick that decides whether Ben needs a check-in, a nudge, or a buddy alert.

Credits

Thanks to the Gemma team for open weights worth building on, Render for a free tier that can host a real agent, and Telegram for a bot API you can drive with plain HTTP.

AI tools assisted with the implementation and with preparing this write-up.

Top comments (1)

Collapse
 
muhammadmurtuzahussain profile image
Muhammad Murtuza Hussain •

interesting project, all the best with the competition. I couldn;t understand much, but i wish you luck