<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Gowshik S</title>
    <description>The latest articles on DEV Community by Gowshik S (@gowshik).</description>
    <link>https://dev.to/gowshik</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3747426%2Fbec0a048-3d73-4c67-bf4f-86a5b92b92a0.jpeg</url>
      <title>DEV Community: Gowshik S</title>
      <link>https://dev.to/gowshik</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gowshik"/>
    <language>en</language>
    <item>
      <title>Rio: An AI Agent That Lives Inside Notion — Voice Task Live Notion Memory</title>
      <dc:creator>Gowshik S</dc:creator>
      <pubDate>Mon, 30 Mar 2026 06:38:40 +0000</pubDate>
      <link>https://dev.to/gowshik/rio-an-ai-agent-that-lives-inside-notion-voice-task-live-notion-memory-4pej</link>
      <guid>https://dev.to/gowshik/rio-an-ai-agent-that-lives-inside-notion-voice-task-live-notion-memory-4pej</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/notion-2026-03-04"&gt;Notion MCP Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Rio is a voice-controlled multi-agent AI system. You speak a task — Rio's agents plan it, execute it on your desktop, research it on the web, and write the result. Every single step of that process lives in your Notion workspace, written in real time, structured, searchable, and yours.&lt;/p&gt;

&lt;p&gt;Notion isn't a plugin here. It's the &lt;strong&gt;mind&lt;/strong&gt; Rio thinks in.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;AI agents are powerful and invisible. They spin up, do work, disappear. You get a final answer with no trace of how it got there — no audit trail, no structured memory, nothing you can build on top of.&lt;/p&gt;

&lt;p&gt;Rio solves that by making Notion the surface where agents think out loud. Every task claimed, every browser step taken, every report written — it all lands in structured Notion databases as it happens, not after the fact.&lt;/p&gt;




&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;iframe src="https://player.vimeo.com/video/1178323142" width="710" height="399"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;What you'll see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A pending task sitting in the &lt;strong&gt;Rio Task Queue&lt;/strong&gt; database&lt;/li&gt;
&lt;li&gt;A voice command to Rio — &lt;em&gt;"Research the top 3 AI agent frameworks and write a comparison"&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Rio Logs&lt;/strong&gt; database populating live — one row per agent step, in real time&lt;/li&gt;
&lt;li&gt;The Creative Agent writing a full formatted Notion page as the final deliverable&lt;/li&gt;
&lt;li&gt;The task row flipping to &lt;code&gt;done&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  How Notion MCP Is Core
&lt;/h2&gt;

&lt;p&gt;There are two distinct ways Notion is used — and both matter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Notion as memory&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every agent writes structured logs as it works. Not at the end — &lt;em&gt;during&lt;/em&gt;. The Orchestrator logs when it claims a task. The UI Navigator logs each browser action. The Creative Agent writes the full report as a Notion page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Every logger.info() becomes this
&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ui_navigator&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;browser_step&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;done&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# → live Notion row: agent name, event, detail, status, timestamp
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;2. Notion as a reasoning tool (via MCP)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Agents actively query and write Notion during execution — not just at completion. The Orchestrator polls the Task Queue for new jobs. The Creative Agent searches existing pages before writing.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;toolset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;MCPToolset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;connection_params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;SseServerParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://mcp.notion.com/sse&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_token&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;tool_filter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;notion-search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;notion-create-pages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query-database&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  No .env Tokens. Anyone Can Use This.
&lt;/h2&gt;

&lt;p&gt;Rio uses a full &lt;strong&gt;OAuth 2.0 + PKCE flow&lt;/strong&gt;. A user clicks "Connect Notion," approves access, and their token lives in session memory. No database IDs are hardcoded anywhere. The moment they connect, Rio auto-creates their &lt;strong&gt;Rio Logs&lt;/strong&gt; and &lt;strong&gt;Rio Task Queue&lt;/strong&gt; databases — zero manual Notion setup.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User clicks Connect Notion
  → /oauth/start  — PKCE verifier generated, redirect to Notion login
  → /oauth/callback — code exchanged, token stored in session memory
  → auto_setup() — Rio Logs DB + Task Queue DB created in their workspace
  → WebSocket init — agents get NotionMemory + MCPToolset injected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The only things in &lt;code&gt;.env&lt;/code&gt; are your app's own &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;client_secret&lt;/code&gt; from notion.so/my-integrations.&lt;/p&gt;


&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RIO — SYSTEM ARCHITECTURE
==========================

                        User
                          |
                    Voice Input
                          |
                          v
              +-------------------------+
              |   Orchestrator Agent    |
              |  polls Notion Task      |
              |  Queue for pending jobs |
              +-------------------------+
                 |         |        |
                 v         v        v
        +------------+ +----------+ +-----------------+
        | Live Agent | | UI Navi- | | Creative Agent  |
        | voice STT  | | gator    | | research, write |
        | + TTS      | | browser  | | full Notion     |
        |            | | desktop  | | pages + reports |
        +------------+ +----------+ +-----------------+
                 |         |        |
                 v         v        v
        +---------------------------------------+
        |            NotionMemory               |
        |  writes every step live to Notion     |
        |  via REST API  (httpx async)          |
        |                                       |
        |   Rio Logs DB       Task Queue DB     |
        |   Agent / Event     Task / Status     |
        |   Detail / Status   Agent / Result    |
        +---------------------------------------+
                          |
                          v
        +---------------------------------------+
        |             MCPToolset                |
        |  lets agents query + search Notion    |
        |  during execution  (SSE transport)    |
        |                                       |
        |   notion-search    query-database     |
        |   notion-create    append-block       |
        +---------------------------------------+
                          |
                          v
              +-------------------------+
              |   User Notion Workspace |
              |   connected via OAuth   |
              |   PKCE — no env tokens  |
              +-------------------------+


OAUTH FLOW
----------

  User clicks "Connect Notion"
          |
          v
  /oauth/start
  generate code_verifier + code_challenge (PKCE S256)
          |
          v
  api.notion.com/v1/oauth/authorize
  user approves Rio access in their own browser
          |
          v
  /oauth/callback
  exchange code + verifier for access_token
  store token in session (in-memory, per user)
          |
          v
  NotionMemory.setup()
  auto-creates Rio Logs DB + Task Queue DB
  in the user workspace if not already present
          |
          v
  MCPToolset.from_server()
  connects to mcp.notion.com/sse
  with user access_token — agents ready

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; Python · Google ADK · FastAPI · WebSockets · Notion REST API · Notion MCP (SSE) · httpx async · Google Cloud Run&lt;/p&gt;


&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;Building agents is easy. Building agents with &lt;strong&gt;observable, persistent memory&lt;/strong&gt; is what actually makes them useful.&lt;/p&gt;

&lt;p&gt;The shift from &lt;code&gt;logger.info()&lt;/code&gt; to &lt;code&gt;await memory.log_step()&lt;/code&gt; sounds trivial — but it completely changes the character of the system. Rio's work is no longer ephemeral. It accumulates. You can go back, search it, build on it, share it. That's what Notion gives it.&lt;/p&gt;

&lt;p&gt;The OAuth decision was also non-negotiable. An agent that only works with hardcoded tokens is a demo. An agent that any person can authorize with their own workspace is a product.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Rio Notion Live Web - &lt;a href="https://notion.gowshik.in/" rel="noopener noreferrer"&gt;Rio-Notion Live Web&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  Source Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Gowshik-S" rel="noopener noreferrer"&gt;
        Gowshik-S
      &lt;/a&gt; / &lt;a href="https://github.com/Gowshik-S/Rio-Notion" rel="noopener noreferrer"&gt;
        Rio-Notion
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;






</description>
      <category>notionchallenge</category>
      <category>ai</category>
      <category>devchallenge</category>
      <category>mcp</category>
    </item>
    <item>
      <title>I built an autonomous voice agent that sees your screen and acts across your apps — here's how</title>
      <dc:creator>Gowshik S</dc:creator>
      <pubDate>Mon, 16 Mar 2026 23:59:05 +0000</pubDate>
      <link>https://dev.to/gowshik/dfsf-1cid</link>
      <guid>https://dev.to/gowshik/dfsf-1cid</guid>
      <description>&lt;p&gt;Most AI agents make the human do the work.&lt;/p&gt;

&lt;p&gt;You craft the prompt carefully. You wait. It misunderstands. You rephrase.&lt;br&gt;
You wait again. That's not autonomy — that's a smarter search bar.&lt;/p&gt;

&lt;p&gt;I wanted to build something different. An agent where you speak once,&lt;br&gt;
naturally — interrupt mid-sentence, change your mind — and it keeps up&lt;br&gt;
without missing a beat. Then acts. Across your applications. Without&lt;br&gt;
follow-up prompts.&lt;/p&gt;

&lt;p&gt;That's Rio.&lt;/p&gt;


&lt;h2&gt;
  
  
  What Rio actually does
&lt;/h2&gt;

&lt;p&gt;Here's the simplest demo I can describe:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I say: &lt;em&gt;"Log this as a support ticket — my order hasn't arrived in 5 days"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Rio listens, identifies it as a support request, extracts the issue,&lt;br&gt;
severity, and category from my voice, then writes a row to Google Sheets&lt;br&gt;
in real time — ticket ID, timestamp, status OPEN — and confirms back&lt;br&gt;
via voice: &lt;em&gt;"Your ticket #007 has been logged."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No typing. No clicking. One instruction, end-to-end.&lt;/p&gt;

&lt;p&gt;And while that's happening, Rio is watching my screen. It knows what&lt;br&gt;
application is open. It can read text off the display. It sees context&lt;br&gt;
I never explicitly told it.&lt;/p&gt;


&lt;h2&gt;
  
  
  The architecture that makes it possible
&lt;/h2&gt;

&lt;p&gt;Rio runs as a split system — and this is the decision that made everything else work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local machine  ←──── binary WebSocket ────→  Cloud Run
  owns hardware                               owns thinking
  mic, screen,                                Gemini Live session,
  UI actions,                                 ADK orchestration,
  file system                                 model routing,
  browser                                     ToolBridge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why split?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because the things that need to be fast (audio capture, screen frames,&lt;br&gt;
UI clicks) need to live close to the hardware. And the things that need&lt;br&gt;
to be powerful (model inference, multi-step planning, API integrations)&lt;br&gt;
need to live in the cloud.&lt;/p&gt;

&lt;p&gt;Mixing them kills both. Splitting them makes both excellent.&lt;/p&gt;


&lt;h2&gt;
  
  
  The binary WebSocket protocol
&lt;/h2&gt;

&lt;p&gt;Everything — audio, video, tool calls — flows over a single WebSocket&lt;br&gt;
connection. I designed a simple binary prefix protocol:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;0x01&lt;/code&gt; + PCM16 mono audio at 16kHz → goes to Gemini Live session&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;0x02&lt;/code&gt; + JPEG screenshot bytes → goes to vision pipeline&lt;/li&gt;
&lt;li&gt;JSON frames → tool calls, tool results, control signals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key insight: audio and video can't share the same queue without&lt;br&gt;
head-of-line blocking. A large JPEG frame will delay audio by hundreds&lt;br&gt;
of milliseconds if they're treated equally.&lt;/p&gt;

&lt;p&gt;Solution: priority queue on the send side. Audio at priority 0, frames&lt;br&gt;
at priority 1. Audio always wins.&lt;/p&gt;


&lt;h2&gt;
  
  
  Native audio — not STT → LLM → TTS
&lt;/h2&gt;

&lt;p&gt;This is the part I'm most proud of architecturally.&lt;/p&gt;

&lt;p&gt;Most voice agents are secretly text agents wearing a voice costume:&lt;br&gt;
speech-to-text → LLM → text-to-speech. The latency is acceptable.&lt;br&gt;
The naturalness isn't.&lt;/p&gt;

&lt;p&gt;Rio uses &lt;code&gt;gemini-live-2.5-flash-native-audio&lt;/code&gt; — Gemini's native audio&lt;br&gt;
model. Voice goes in, voice comes out. No transcription middle layer.&lt;br&gt;
The model hears tone, pace, hesitation. It responds like a conversation,&lt;br&gt;
not a query.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tradeoff:&lt;/strong&gt; native audio models are unreliable for function calling.&lt;br&gt;
I had to route all tool execution through a text-based orchestrator via&lt;br&gt;
&lt;code&gt;live_model_tools=False&lt;/code&gt;. There's a hidden parsing layer between what&lt;br&gt;
the model says and what the tool dispatcher hears. Getting that clean&lt;br&gt;
took the most iteration of anything in this build.&lt;/p&gt;


&lt;h2&gt;
  
  
  True interruptions via Silero VAD
&lt;/h2&gt;

&lt;p&gt;Real interruption handling is harder than it sounds.&lt;/p&gt;

&lt;p&gt;The naive approach: detect silence, stop playback. This breaks constantly&lt;br&gt;
— background noise, breath sounds, pauses mid-thought all trigger false&lt;br&gt;
interruptions.&lt;/p&gt;

&lt;p&gt;The right approach: Silero VAD running in a &lt;code&gt;sounddevice&lt;/code&gt; callback thread,&lt;br&gt;
detecting actual voice activity with a trained model. When it triggers,&lt;br&gt;
it hands off to the asyncio event loop via &lt;code&gt;call_soon_threadsafe&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The gotcha: getting that handoff fast enough to feel instantaneous.&lt;br&gt;
VAD detection to playback stop needs to be under ~100ms or it feels&lt;br&gt;
laggy. The threading boundary between &lt;code&gt;sounddevice&lt;/code&gt; callbacks and asyncio&lt;br&gt;
is where most of that latency lives.&lt;/p&gt;


&lt;h2&gt;
  
  
  The ToolBridge pattern
&lt;/h2&gt;

&lt;p&gt;This is the pattern I'm most likely to reuse on future projects.&lt;/p&gt;

&lt;p&gt;The problem: Gemini running in Cloud Run needs to execute actions on&lt;br&gt;
my local machine. It can't do that directly. So I built a bridge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Cloud side — when Gemini calls a tool:
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;bridge_tool_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;call_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;future&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;call_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;future&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool_call&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;call_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;args&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;

&lt;span class="c1"&gt;# Local side — when tool_call arrives:
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_tool_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;execute_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;args&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool_result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every tool is a closured async function, isolated per WebSocket connection.&lt;br&gt;
Adding a new tool is 10 lines. The cloud model never knows it's talking&lt;br&gt;
to a local machine.&lt;/p&gt;


&lt;h2&gt;
  
  
  Struggle detection — ML inside the agent
&lt;/h2&gt;

&lt;p&gt;One thing I haven't seen in other agent demos: behavioral adaptation.&lt;/p&gt;

&lt;p&gt;Rio runs a scikit-learn classification model in real time, trained on&lt;br&gt;
user interaction patterns — retry frequency, error rate, response latency&lt;br&gt;
variance. When the model predicts a user is struggling, Rio shifts its&lt;br&gt;
response style proactively. Simpler language. More scaffolding. Without&lt;br&gt;
being asked.&lt;/p&gt;

&lt;p&gt;The hard part was feature engineering. Hesitation alone isn't struggle.&lt;br&gt;
A retry alone isn't struggle. The signal lives in the combination —&lt;br&gt;
and finding that combination required building a small labeled dataset&lt;br&gt;
from my own interaction logs.&lt;/p&gt;

&lt;p&gt;The next evolution: wiring this directly into ADK's &lt;code&gt;LiveRequestQueue&lt;/code&gt;&lt;br&gt;
as a streaming tool so the adaptation happens inside the agent loop,&lt;br&gt;
not as an external observer.&lt;/p&gt;


&lt;h2&gt;
  
  
  The graceful degradation model
&lt;/h2&gt;

&lt;p&gt;Free-tier Gemini API limits are real. 30 RPM disappears fast.&lt;/p&gt;

&lt;p&gt;I built a token bucket rate limiter with 4 degradation levels:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;Rio's behavior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Healthy&lt;/td&gt;
&lt;td&gt;Full capability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;70% bucket&lt;/td&gt;
&lt;td&gt;Reduce screenshot frequency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;40% bucket&lt;/td&gt;
&lt;td&gt;Disable proactive vision&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;10% bucket&lt;/td&gt;
&lt;td&gt;Voice only, no tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Empty&lt;/td&gt;
&lt;td&gt;Graceful hold with user message&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The agent never crashes. It degrades. That distinction matters a lot&lt;br&gt;
in a live demo.&lt;/p&gt;


&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Start with the protocol design.&lt;/strong&gt; I retrofitted the binary frame protocol&lt;br&gt;
onto an existing JSON-only WebSocket. It worked, but doing it upfront would&lt;br&gt;
have saved two days of refactoring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wire the struggle detector earlier.&lt;/strong&gt; I built it as a standalone pipeline&lt;br&gt;
first, then realized it needed to be inside the agent loop to be useful.&lt;br&gt;
That's a lesson about where ML belongs in agent systems — not as an&lt;br&gt;
observer, but as an actor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;InMemorySessionService is a trap.&lt;/strong&gt; Fine for development. The moment&lt;br&gt;
Cloud Run cold-starts mid-conversation, your session handle is gone.&lt;br&gt;
Design for persistence from day one.&lt;/p&gt;


&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The current build is one powerful agent. The next version is four:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Orchestrator&lt;/strong&gt; — ADK + Gemini 2.5 Pro, plans and delegates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live Agent&lt;/strong&gt; — owns all voice and vision&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UI Navigator&lt;/strong&gt; — Playwright + Computer Use API, true visual grounding&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Creative Agent&lt;/strong&gt; — interleaved output, mixed-media responses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Connected via A2A protocol with Agent Cards. Rio as a node in a&lt;br&gt;
larger agent network, not a standalone tool.&lt;/p&gt;


&lt;h2&gt;
  
  
  Try it / follow along
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Gowshik-S/Gemini-Live-Agent" rel="noopener noreferrer"&gt;https://github.com/Gowshik-S/Gemini-Live-Agent&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The repo includes a one-command Cloud Run deployment via &lt;code&gt;service.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud run services replace cloud/service.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're building something in the agent space — especially anything&lt;br&gt;
involving Gemini Live API or ADK — I'd love to compare notes.&lt;br&gt;
Drop a comment or find me on LinkedIn.&lt;/p&gt;

&lt;p&gt;One human. One instruction. Rio does the rest.&lt;/p&gt;

</description>
      <category>geminiliveagentchallenge</category>
      <category>ai</category>
      <category>agents</category>
    </item>
    <item>
      <title>When Disasters Strike, Cell Towers Fall First — So I Built a Mesh Network for Coastal Communities</title>
      <dc:creator>Gowshik S</dc:creator>
      <pubDate>Mon, 02 Mar 2026 07:15:56 +0000</pubDate>
      <link>https://dev.to/gowshik/when-disasters-strike-cell-towers-fall-first-so-i-built-a-mesh-network-for-coastal-communities-4gn9</link>
      <guid>https://dev.to/gowshik/when-disasters-strike-cell-towers-fall-first-so-i-built-a-mesh-network-for-coastal-communities-4gn9</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/weekend-2026-02-28"&gt;DEV Weekend Challenge: Community&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Community
&lt;/h2&gt;

&lt;p&gt;Over 2 billion people live within 100km of a coastline. Fishermen in Indonesia, families in the Philippines, communities in Haiti and India — people who face cyclones, tsunamis, and floods not as rare events, but as a way of life.&lt;/p&gt;

&lt;p&gt;Every time disaster strikes, the same thing happens: &lt;strong&gt;the cell towers go down first.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I grew up in India, where this hit close to home. During Cyclone Ockhi in 2017, dozens of fishermen died at sea off the Tamil Nadu coast. Not because rescue teams didn't exist — because when the storm hit, towers went down and no one knew where they were. Families waited on shores for days. Rescue teams deployed blind.&lt;/p&gt;

&lt;p&gt;That moment stuck with me. We had phones. We had apps. It wasn't a technology failure — it was an infrastructure failure. The system built to connect people in emergencies was the first thing the emergency destroyed.&lt;/p&gt;

&lt;p&gt;This is not just India's story. It's every coastal community that has ever lived through a major disaster. &lt;strong&gt;Ocean Sentinels is built for all of them.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Ocean Sentinels is an Android app that lets coastal communities report hazards, coordinate rescues, and stay connected — &lt;strong&gt;even when there's no internet.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The core idea: turn ordinary Android phones into a disaster-proof relay network using Bluetooth. When a user submits a hazard report offline, it hops phone to phone through Bluetooth — up to &lt;strong&gt;~400 metres per hop&lt;/strong&gt; — until any device in the chain finds internet and uploads it to the server.&lt;br&gt;
&lt;strong&gt;No cell towers. No WiFi. No special hardware. &lt;em&gt;Just phones&lt;/em&gt;.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  [ Phone A ] ---BLE 400m---&amp;gt; [ Phone B ] ---BLE 400m---&amp;gt; [ Phone C: Has Internet ]
  No signal                   No signal                    Uploads to server ✓
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;What the app does:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Report coastal hazards (high waves, flooding, lost boats) with GPS + photo&lt;/li&gt;
&lt;li&gt;Relay reports offline via BLE mesh — tested at &lt;strong&gt;~400m in open areas&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Live map with real-time incident markers&lt;/li&gt;
&lt;li&gt;Role-based access for Citizens, Rescue Teams, Authorities, and Admins&lt;/li&gt;
&lt;li&gt;Push notifications via Firebase even with the app closed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's also a web dashboard for authorities who need a larger screen — but the Android app is the real lifeline. The web requires internet. &lt;strong&gt;The mesh doesn't.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The cover video above gives a full walkthrough of the app.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Real-world BLE mesh test — three phones communicating at ~400m in open area,no internet, report relayed successfully:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw5q1ertjz2e9f4ykpb2h.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw5q1ertjz2e9f4ykpb2h.gif" alt="BLE Mesh Live Test - Open Area ~400m" width="240" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;App walkthrough — reporting, map, and mesh screens:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F31qvzfarwmkjbx8oxpnx.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F31qvzfarwmkjbx8oxpnx.gif" alt="App Walkthrough" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  &lt;a href="https://github.com/Gowshik-S/Ocean-Sentinels/releases/download/Beta/Ocean-Sentinels.V1.1.apk" rel="noopener noreferrer"&gt;
     Download Ocean Sentinels APK (Android)
  &lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;If the download is slow use this link (Alternate Download Link) &lt;br&gt;
[&lt;a href="https://drive.usercontent.google.com/download?id=176DMfxRhUPVtEP4dp93aTqyDKpibZ01B&amp;amp;export=download" rel="noopener noreferrer"&gt;Ocean Sentinels v1.1&lt;/a&gt;]&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Better Use the APK version, because our primary goal is build a Application.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  &lt;a href="https://sih.gowshik.in/pages/index.html" rel="noopener noreferrer"&gt;
     Ocean Sentinels Web
  &lt;/a&gt;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;iOS is Developed but can't be exported due to some limitations. Refer the github repo for source code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test Credentials&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Email&lt;/th&gt;
&lt;th&gt;Password&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Citizen&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:sihcitizen@vi.com"&gt;sihcitizen@vi.com&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;SIH@2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rescue&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:sihrescue@vi.com"&gt;sihrescue@vi.com&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Ocean@123&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Admin&lt;/td&gt;
&lt;td&gt;OceanAdmin1&lt;/td&gt;
&lt;td&gt;admin&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Works on both the Android app and web dashboard.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Screenshots
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Home Dashboard — quick action cards:&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyxqa4wpbbt9s4a4v10ax.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyxqa4wpbbt9s4a4v10ax.png" alt="Home Dashboard" width="800" height="1777"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Report Incident — GPS, hazard type, photo upload:&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3k89novf33azo8zdqvi7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3k89novf33azo8zdqvi7.png" alt="Report Incident" width="800" height="1777"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;BLE Mesh Screen — live peers and relay status:&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkcvh29856puig1atiqef.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkcvh29856puig1atiqef.png" alt="BLE Mesh Screen" width="800" height="1777"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Live Map — real-time incident markers:&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6v2eyiqvtk5ubalzke2b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6v2eyiqvtk5ubalzke2b.png" alt="Live Map" width="744" height="1653"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Gowshik-S" rel="noopener noreferrer"&gt;
        Gowshik-S
      &lt;/a&gt; / &lt;a href="https://github.com/Gowshik-S/Ocean-Sentinels" rel="noopener noreferrer"&gt;
        Ocean-Sentinels
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      About Ocean Sentinels is an offline-first disaster management platform designed to save lives in coastal regions. It solves the critical problem of connectivity failure during disasters by implementing a custom "Store-Carry-Forward" Bluetooth Mesh Network.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Ocean Sentinels - A Coastal Safety Network&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A full-stack coastal safety and hazard reporting platform built for coastal communities worldwide. The system enables real-time maritime incident reporting, response coordination, and offline BLE mesh networking for areas with limited or no connectivity — keeping communities connected when disasters take down
the infrastructure.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Table of Contents&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Gowshik-S/Ocean-Sentinels#overview" rel="noopener noreferrer"&gt;Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Gowshik-S/Ocean-Sentinels#architecture" rel="noopener noreferrer"&gt;Architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Gowshik-S/Ocean-Sentinels#project-structure" rel="noopener noreferrer"&gt;Project Structure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Gowshik-S/Ocean-Sentinels#backend-api" rel="noopener noreferrer"&gt;Backend API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Gowshik-S/Ocean-Sentinels#frontend-web-app" rel="noopener noreferrer"&gt;Frontend Web App&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Gowshik-S/Ocean-Sentinels#android-app" rel="noopener noreferrer"&gt;Android App&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Gowshik-S/Ocean-Sentinels#ble-mesh-network" rel="noopener noreferrer"&gt;BLE Mesh Network&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Gowshik-S/Ocean-Sentinels#getting-started" rel="noopener noreferrer"&gt;Getting Started&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Gowshik-S/Ocean-Sentinels#deployment" rel="noopener noreferrer"&gt;Deployment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Gowshik-S/Ocean-Sentinels#configuration" rel="noopener noreferrer"&gt;Configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Gowshik-S/Ocean-Sentinels#api-reference" rel="noopener noreferrer"&gt;API Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Gowshik-S/Ocean-Sentinels#license" rel="noopener noreferrer"&gt;License&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Overview&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Ocean Sentinels provides a unified platform for coastal hazard reporting across worldwide. It supports four user roles -- Public citizens, Authorities, Rescue Teams, and Administrators -- each with dedicated consoles and workflows.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Core capabilities:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Real-time incident reporting with GPS coordinates and photo evidence&lt;/li&gt;
&lt;li&gt;Live interactive map (Mapbox) with incident markers and monitoring zones&lt;/li&gt;
&lt;li&gt;Role-based dashboards for incident verification, response deployment, and analytics&lt;/li&gt;
&lt;li&gt;BLE mesh networking for offline hazard report relay between Android devices&lt;/li&gt;
&lt;li&gt;Weather…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Gowshik-S/Ocean-Sentinels" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;The Android app is built with &lt;strong&gt;Kotlin and Jetpack Compose&lt;/strong&gt;, with Hilt for dependency injection, Room for local offline storage, and Mapbox for the live map. The BLE mesh networking layer — the offline relay system — is also entirely written in Kotlin, running as a foreground service so it keeps relaying reports even when the app is in the background. Firebase handles push notifications so rescue teams are alerted even with the app closed.&lt;/p&gt;

&lt;p&gt;The web dashboard for authorities and rescue teams is powered by a &lt;strong&gt;FastAPI + PostgreSQL&lt;/strong&gt; backend — handling incident reports, role-based access control, and WebSocket live updates for real-time map monitoring.&lt;/p&gt;

&lt;p&gt;The hardest part was the BLE mesh layer. A few things I learned:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hop-count TTL doesn't work in disasters.&lt;/strong&gt; A relay chain can be 50+ devices long before reaching internet. I switched to 72-hour time-based expiry so messages survive however long the chain needs to be.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Android 6+ hides MAC addresses.&lt;/strong&gt; You can't use Bluetooth MACs to identify &lt;br&gt;
devices. I generate a SHA-256 fingerprint from the device ID instead — &lt;br&gt;
persistent and privacy-safe.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reports are stored in Room first.&lt;/strong&gt; Even if mesh relay takes hours, the report &lt;br&gt;
is never lost. It uploads automatically when connectivity returns.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;When the network goes down, the mesh goes up.&lt;/em&gt; &lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Ocean Sentinels - An Offline App for Worldwide Costal Communities</title>
      <dc:creator>Gowshik S</dc:creator>
      <pubDate>Mon, 16 Feb 2026 07:59:56 +0000</pubDate>
      <link>https://dev.to/gowshik/ocean-sentinels-2ojo</link>
      <guid>https://dev.to/gowshik/ocean-sentinels-2ojo</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/github-2026-01-21"&gt;GitHub Copilot CLI Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Ocean Sentinels&lt;/strong&gt; — A full-stack coastal safety platform (Web + Android) with an offline BLE Bluetooth mesh network for disaster scenarios.&lt;/p&gt;

&lt;p&gt;When natural disasters knock out cell towers, coastal communities are left stranded. Ocean Sentinels solves this by turning ordinary Android phones into a &lt;strong&gt;Bluetooth mesh network&lt;/strong&gt; — hazard reports hop phone-to-phone until &lt;em&gt;any&lt;/em&gt; device finds connectivity and uploads to the server, alerting rescue teams and authorities instantly.&lt;/p&gt;

&lt;p&gt;Ocean Sentinels is personal to me — coastal communities in India are among the most disaster-prone in the world, and the cruel irony is that when disasters strike hardest, the communication infrastructure goes down first. I built this because I believe no one should be unreachable just because a cell tower fell.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Platform
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Web App&lt;/strong&gt; — Interactive Mapbox map with real-time incident markers, role-based dashboards for 4 user types (Public, Authority, Rescue Team, Admin), WebSocket live notifications, analytics charts, and responsive design&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Android App&lt;/strong&gt; — 21 Jetpack Compose screens built with Material3, offline-first architecture with Room DB caching, Firebase push notifications, GPS location tracking, and the BLE mesh networking layer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend&lt;/strong&gt; — FastAPI + PostgreSQL with async operations, 16+ REST endpoints, JWT authentication, role-based access control, and mesh message deduplication&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The BLE Mesh Network
&lt;/h3&gt;

&lt;p&gt;This is the core innovation. When there's no internet:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A user submits a hazard report on their phone&lt;/li&gt;
&lt;li&gt;The report broadcasts over BLE to nearby phones (~400m range using Coded PHY S8)&lt;/li&gt;
&lt;li&gt;Each phone relays it forward using store-carry-forward protocol&lt;/li&gt;
&lt;li&gt;When ANY phone in the chain gets internet → the report uploads to the server → authorities are alerted&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;No special hardware needed. Just phones.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cover Video&lt;/strong&gt; :&lt;br&gt;


  &lt;iframe src="https://www.youtube.com/embed/vLdE3n-UN5I"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;Technical specs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Coded PHY S8&lt;/strong&gt; — 8x forward error correction, extends BLE range to ~400m (vs 100m standard)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;72-hour time-based message expiry&lt;/strong&gt; instead of hop-count TTL — because in a disaster, a message chain might be 50+ devices long before reaching internet&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Triple-layer deduplication&lt;/strong&gt; — relay path tracking + in-memory LRU cache (10K entries) + database-level dedup&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SHA-256 deterministic message IDs&lt;/strong&gt; — same report from same device always generates the same ID&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dual PHY fallback&lt;/strong&gt; — tries Coded PHY first for range, gracefully degrades to 1M PHY for older devices&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tech Stack
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Stack&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;Python 3.11, FastAPI, SQLAlchemy 2.0 (async), PostgreSQL, JWT, WebSocket&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;Vanilla JS, Mapbox GL JS, CSS3, role-based navigation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Android&lt;/td&gt;
&lt;td&gt;Kotlin, Jetpack Compose, Material3, Hilt, Room, Retrofit, Mapbox SDK, Firebase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mesh&lt;/td&gt;
&lt;td&gt;BLE 5.0 Coded PHY S8, GATT server/client, SHA-256 dedup, foreground service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deploy&lt;/td&gt;
&lt;td&gt;Render (backend), Vercel (frontend)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://vid.gowshik.in" rel="noopener noreferrer"&gt;https://vid.gowshik.in&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live Test at Open Area&lt;/strong&gt; :&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw5q1ertjz2e9f4ykpb2h.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw5q1ertjz2e9f4ykpb2h.gif" alt="Live Test at Open Area"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live Test In Screen&lt;/strong&gt; : &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsz2zmfw9r9b1p7tdd2rm.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsz2zmfw9r9b1p7tdd2rm.gif" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live Demo&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F31qvzfarwmkjbx8oxpnx.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F31qvzfarwmkjbx8oxpnx.gif" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live Web App:&lt;/strong&gt; [&lt;a href="https://sih.gowshik.in/pages/index.html" rel="noopener noreferrer"&gt;Ocean Sentinels - Web&lt;/a&gt;]&lt;br&gt;
&lt;strong&gt;GitHub Repo:&lt;/strong&gt; &lt;a href="https://github.com/Gowshik-S/Ocean-Sentinels" rel="noopener noreferrer"&gt;https://github.com/Gowshik-S/Ocean-Sentinels&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;APK Link:&lt;/strong&gt; [&lt;a href="https://drive.google.com/uc?export=download&amp;amp;id=176DMfxRhUPVtEP4dp93aTqyDKpibZ01B" rel="noopener noreferrer"&gt;Ocean Sentinels - APK&lt;/a&gt;]&lt;br&gt;
&lt;strong&gt;APK Link: (Alternate Link)&lt;/strong&gt; [&lt;a href="https://github.com/Gowshik-S/Ocean-Sentinels/releases/download/Beta/Ocean-Sentinels.V1.1.apk" rel="noopener noreferrer"&gt;Ocean Sentinels - APK&lt;/a&gt;]&lt;br&gt;
&lt;strong&gt;IOS Link:&lt;/strong&gt; Developed but can't be exported due to some limitations. Refer the github repo for source code. &lt;/p&gt;
&lt;h3&gt;
  
  
  Test Credentials
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Username/Email&lt;/th&gt;
&lt;th&gt;Password&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Citizen&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:sihcitizen@vi.com"&gt;sihcitizen@vi.com&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;SIH@2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rescue&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:sihrescue@vi.com"&gt;sihrescue@vi.com&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Ocean@123&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Admin&lt;/td&gt;
&lt;td&gt;OceanAdmin1&lt;/td&gt;
&lt;td&gt;admin&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Works on both the Web App and Android APK.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Screenshots
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0qo2ohmo9glsugu8wad8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0qo2ohmo9glsugu8wad8.png" alt="Index Page for Ocean Sentinels"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Web Analytics (Public) :&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4uyt1z78x5pygjdbi0a3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4uyt1z78x5pygjdbi0a3.png" alt="Web Analytics (Public)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Authority Analytics :&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyunwlo06nkr55sip7jyz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyunwlo06nkr55sip7jyz.png" alt="Authority Analytic"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Android - Home Screen (dashboard with action cards):&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyxqa4wpbbt9s4a4v10ax.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyxqa4wpbbt9s4a4v10ax.png" alt="Android - Home Screen"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Android - Report Incident (form with GPS, hazard type)&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3k89novf33azo8zdqvi7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3k89novf33azo8zdqvi7.png" alt="Android - Report Incident (form with GPS, hazard type)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Android - Mesh Network Screen (BLE peers, relay status)&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkcvh29856puig1atiqef.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkcvh29856puig1atiqef.png" alt="Android - Mesh Network Screen (BLE peers, relay status)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Android - Map View (Mapbox with markers)&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6v2eyiqvtk5ubalzke2b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6v2eyiqvtk5ubalzke2b.png" alt="Android - Map View (Mapbox with markers)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Architecture Diagram (phone-to-phone mesh relay)&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgcqqq39wewb9xip4yewd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgcqqq39wewb9xip4yewd.png" alt="Architecture Diagram (phone-to-phone mesh relay)"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  My Experience with GitHub Copilot CLI
&lt;/h2&gt;

&lt;p&gt;GitHub Copilot CLI was instrumental across every layer of Ocean Sentinels. Here's how it shaped my development:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub Copilot CLI terminal examples&lt;/strong&gt; :&lt;/p&gt;
&lt;h3&gt;
  
  
  Using &lt;code&gt;gh copilot&lt;/code&gt; in the Terminal
&lt;/h3&gt;

&lt;p&gt;The challenge is built around GitHub Copilot &lt;strong&gt;CLI&lt;/strong&gt; — and here's where it directly shaped my workflow:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Designing the BLE deduplication strategy:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gh copilot suggest &lt;span class="s2"&gt;"how do I deduplicate BLE mesh messages across 50+ relay hops in Android without using hop count TTL"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Copilot suggested time-based TTL (72hr) + SHA-256 deterministic IDs + LRU in-memory cache — the exact triple-layer system I implemented.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Debugging Coded PHY negotiation failure:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gh copilot explain &lt;span class="s2"&gt;"BluetoothAdapter.listenUsingInsecureL2capChannel returning null on Android 11 with Coded PHY"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;It explained the Android 11 BLE stack quirk and suggested the dual-PHY fallback pattern I now use.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Generating FastAPI async SQLAlchemy boilerplate:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gh copilot suggest &lt;span class="s2"&gt;"FastAPI async SQLAlchemy 2.0 endpoint with JWT role-based access control for incident reporting"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Scaffolded the entire endpoint pattern — I reused this template across all 16+ backend routes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  BLE Mesh Layer — The Hardest Part
&lt;/h3&gt;

&lt;p&gt;Building a Bluetooth mesh network from scratch is complex. Copilot CLI helped me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Design the &lt;code&gt;BleMeshManager&lt;/code&gt; that runs a GATT server and BLE scanner simultaneously&lt;/li&gt;
&lt;li&gt;Implement the triple-layer deduplication strategy (relay path + LRU cache + DB check)&lt;/li&gt;
&lt;li&gt;Build the &lt;code&gt;DeviceIdentifier&lt;/code&gt; system — Android 6+ masks real MAC addresses, so Copilot suggested a &lt;code&gt;SHA-256(deviceId + androidId)&lt;/code&gt; fingerprint approach&lt;/li&gt;
&lt;li&gt;Debug BLE Coded PHY negotiation and fallback logic when devices don't support PHY S8&lt;/li&gt;
&lt;li&gt;Architect the time-based TTL system — Copilot understood why hop-count TTL fails in disaster scenarios and suggested 72-hour expiry instead&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Full-Stack Development at Speed
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; Generated all FastAPI endpoints, SQLAlchemy async models, Pydantic schemas, and JWT middleware with role-based guards — &lt;code&gt;gh copilot suggest&lt;/code&gt; understood the access control patterns I needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; Role-based navigation manager, Mapbox GL custom markers, WebSocket reconnection handlers — Copilot scaffolded the boilerplate so I focused on user experience&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Android:&lt;/strong&gt; 21 Compose screens with Hilt dependency injection, Room database entities, Retrofit API client — it understood my clean architecture layers and generated consistent code across data/domain/presentation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cross-Layer Intelligence
&lt;/h3&gt;

&lt;p&gt;The most impressive moment: while working on mesh message dedup in the Android &lt;code&gt;BleMeshManager&lt;/code&gt;, Copilot correctly suggested that the backend's Incident model also needed a &lt;code&gt;mesh_message_id&lt;/code&gt; unique constraint. It understood the end-to-end flow — BLE relay → API upload → database dedup — without me explicitly describing it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Impact on Development
&lt;/h3&gt;

&lt;p&gt;What would have taken weeks of researching BLE specs, GATT protocol documentation, and Android Bluetooth stack quirks was compressed into days. Copilot CLI didn't just autocomplete — it understood context across files, layers, and even the research paper concepts I was implementing.&lt;/p&gt;

&lt;p&gt;It generated the &lt;code&gt;NetworkModule&lt;/code&gt;, &lt;code&gt;DatabaseModule&lt;/code&gt;, and &lt;code&gt;AppModule&lt;/code&gt; with proper &lt;code&gt;@Provides&lt;/code&gt; and &lt;code&gt;@Singleton&lt;/code&gt; annotations — all correctly wired.&lt;/p&gt;

&lt;h3&gt;
  
  
  BLE Mesh Network — &lt;strong&gt;&lt;em&gt;Where Copilot Truly Shined&lt;/em&gt;&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The mesh layer was the most complex part. I had the IEEE BitChat paper as reference, but translating academic protocols into Android BLE code is a different beast.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────────────────┐
│                           DEVICE ROLES                                   │
└─────────────────────────────────────────────────────────────────────────┘

┌──────────────┐         ┌──────────────┐         ┌──────────────┐
│   GATEWAY    │         │    RELAY     │         │   NORMAL     │
│    DEVICE    │         │    NODE      │         │    NODE      │
├──────────────┤         ├──────────────┤         ├──────────────┤
│  Internet    │         │ No Internet  │         │ No Internet  |
│              │         │              │         │              │
│  Relay       │◄───────►│ Forward      │◄───────►│ Send         │
│  Upload      │         │ Receive      │         │ Receive      │
│  Download    │         │ Re-broadcast │         | Broadcast    │
└──────────────┘         └──────────────┘         └──────────────┘

  * Any device can become Gateway if it gets internet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  BLE Mesh Relay Network (Offline Mode)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  +------------------+                +------------------+
  |  Offline Device  | ----BLE------&amp;gt; |   Relay Peer     | ----BLE-----&amp;gt;  ...
  |                  |                |                  |
  |  Creates Report  |                |  Receives Msg    |
  |  Stores in Room  |                |  SHA-256 Dedup   |
  |  Broadcasts via  |                |  Checks 72h      |
  |  BLE GATT Server |                |  Expiry Window   |
  +------------------+                +--------+---------+
                                               |
                                   +-----------+-----------+
                                   |                       |
                              No Internet             Has Internet
                                   |                       |
                                   v                       v
                          +----------------+    +--------------------+
                          | Continue Relay |    | Upload to Backend  |
                          | to Next Peers  |    | (Server Dedup via  |
                          | (Append to     |    |  mesh_message_id)  |
                          |  Relay Path)   |    |                    |
                          +----------------+    +--------------------+

  Mesh Specifications:
  +------------------------+--------------------------+
  | Parameter              | Value                    |
  +------------------------+--------------------------+
  | Max Connections        | 7 simultaneous peers     |
  | Fragment Size          | 469 bytes                |
  | Max Packet Size        | 512 bytes                |
  | Dedup Cache            | 10,000 message IDs (LRU) |
  | Message Expiry         | 72 hours (time-based)    |
  | Peer Stale Timeout     | 180 seconds              |
  | Scan Restart Interval  | 30 seconds               |
  | PHY Strategy           | Coded PHY (long range),  |
  |                        | 1M PHY fallback          |
  +------------------------+--------------------------+

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  📡 BLE Mesh Protocol Stack
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────────┐
│                     MESH APPLICATION LAYER                      │
├─────────────────────────────────────────────────────────────────┤
│  ┌────────────┐  ┌────────────┐  ┌────────────┐  ┌────────────┐ │
│  │  Incident  │  │   Status   │  │   Sync     │  │   Ack      │ │
│  │  Report    │  │  Update    │  │  Request   │  │  Message   │ │
│  └─────┬──────┘  └─────┬──────┘  └─────┬──────┘  └─────┬──────┘ │
└────────┼───────────────┼───────────────┼───────────────┼────────┘
         │               │               │               │
         ▼               ▼               ▼               ▼
┌─────────────────────────────────────────────────────────────────┐
│                     MESH TRANSPORT LAYER                        │
├─────────────────────────────────────────────────────────────────┤
│  Serialization: Protocol Buffers → Byte Array                   │
│  Fragmentation: 469 bytes per fragment (512-byte BLE limit)     │
│  Reassembly: Sequence numbers + CRC verification                │
│  Encryption: ChaCha20-Poly1305 (via libsodium)                  │
└─────────────────────────────────────────────────────────────────┘
         │
         ▼
┌─────────────────────────────────────────────────────────────────┐
│                     MESH NETWORK LAYER                          │
├─────────────────────────────────────────────────────────────────┤
│  TTL Management: Time Based upto 72Hrs                          |         
│  Deduplication: SHA-256 message IDs, 10K cache                  │
│  Flood Control: Randomized delays to prevent collisions         │
│  Peer Discovery: mDNS/Zeroconf for local service discovery      │
└─────────────────────────────────────────────────────────────────┘
         │
         ▼
┌─────────────────────────────────────────────────────────────────┐
│                     BLE PHYSICAL LAYER                          │
├─────────────────────────────────────────────────────────────────┤
│  ┌──────────────────┐    ┌──────────────────┐                   │
│  │   CODED PHY S=8  │    │    STANDARD PHY  │                   │
│  │    (Primary)     │    │    (Fallback)    │                   │
│  ├──────────────────┤    ├──────────────────┤                   │
│  │ • Long range     │    │ • Standard range │                   │
│  │ • ~400m outdoors │    │ • ~100m outdoors │                   │
│  │ • 125 kbps speed │    │ • 1 Mbps speed   │                   | 
│  │ • High FEC       │    │ • Lower latency  │                   │
│  └──────────────────┘    └──────────────────┘                   │
│                                                                 │
│  Timeslot: Each node scans 30ms, then advertises 100ms          │
└─────────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;GATT Server + Scanner Setup:&lt;/strong&gt;&lt;br&gt;
I used Copilot in VS Code to help write &lt;code&gt;BleMeshManager.kt&lt;/code&gt; — it understood that I needed a BLE GATT server advertising a custom service UUID while simultaneously scanning for other devices. When I typed the GATT server callback scaffold, Copilot auto-completed the entire &lt;code&gt;onConnectionStateChange&lt;/code&gt;, &lt;code&gt;onCharacteristicReadRequest&lt;/code&gt;, and &lt;code&gt;onCharacteristicWriteRequest&lt;/code&gt; handlers with proper response codes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Device Identification Problem:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&amp;gt; Android 6+ randomizes MAC addresses, so I couldn't use them to identify devices. &lt;/li&gt;
&lt;li&gt;&amp;gt; Need a persistent unique device identifier since Android 6+ masks real MAC addresses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Built for coastal communities worldwide. When the network goes down, the mesh goes up.&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Built within 2 weeks. Impossible without Github CLI !&lt;/em&gt;&lt;/strong&gt; &lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>githubchallenge</category>
      <category>cli</category>
      <category>githubcopilot</category>
    </item>
  </channel>
</rss>
