<?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: Mahasvan Mohan</title>
    <description>The latest articles on DEV Community by Mahasvan Mohan (@mahasvan).</description>
    <link>https://dev.to/mahasvan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F99mvlsfu5tfj9m7ku25d.png</url>
      <title>DEV Community: Mahasvan Mohan</title>
      <link>https://dev.to/mahasvan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mahasvan"/>
    <language>en</language>
    <item>
      <title>Hacktoberfest Week 1 - Anti-Router</title>
      <dc:creator>Mahasvan Mohan</dc:creator>
      <pubDate>Thu, 08 Oct 2026 05:53:15 +0000</pubDate>
      <link>https://dev.to/mahasvan/hacktoberfest-week-1-anti-router-1oe8</link>
      <guid>https://dev.to/mahasvan/hacktoberfest-week-1-anti-router-1oe8</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05"&gt;Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;touch-grass&lt;/strong&gt; is the Anti-Router: an offline-first wanderlust agent that plans exploratory pedestrian &lt;em&gt;dérives&lt;/em&gt; — scavenger-hunt walks optimized for discovery, not speed.&lt;/p&gt;

&lt;p&gt;Instead of telling you "turn left in 100 feet," it generates navigational &lt;strong&gt;riddles&lt;/strong&gt;: &lt;em&gt;"Face north. Somewhere north-west of here waits a fountain — follow the quietest path you can find, and let me know when you arrive."&lt;/em&gt; You have to actually look at the world — buildings, trees, water, art — to orient yourself. The phone stays in your pocket.&lt;/p&gt;

&lt;p&gt;It's for anyone who walks the same three blocks every day and wants their own neighborhood to feel foreign again.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;uv run touch-grass derive &lt;span class="s2"&gt;"Carmel-by-the-Sea, California"&lt;/span&gt; &lt;span class="nt"&gt;--minutes&lt;/span&gt; 45
&lt;span class="go"&gt;Dérive: 2 waypoints, ~2.70 mi (budget 2.25 mi).
  - a tree @ (36.54186, -121.92063)
  - a tree @ (36.54569, -121.92933)

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;uv run touch-grass track   &lt;span class="c"&gt;# feed "lat,lon" GPS fixes on stdin&lt;/span&gt;
&lt;span class="go"&gt;You found a tree. Face north. Somewhere north-west of here waits a tree —
turn toward the north-west, follow the quietest path you can find...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;On macOS, &lt;code&gt;track&lt;/code&gt; also speaks each riddle aloud via &lt;code&gt;say&lt;/code&gt;. A fully offline &lt;code&gt;touch-grass demo&lt;/code&gt; simulates the whole loop — graph, routing, geofences, riddles — on a synthetic city.&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/Mahasvan" rel="noopener noreferrer"&gt;
        Mahasvan
      &lt;/a&gt; / &lt;a href="https://github.com/Mahasvan/touch-grass" rel="noopener noreferrer"&gt;
        touch-grass
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &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;touch-grass — the Anti-Router&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Offline-first wanderlust agent: plans exploratory pedestrian dérives
(scavenger-hunt walks) from cached OpenStreetMap data, then narrates
navigational riddles instead of turn-by-turn directions.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Setup&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;uv sync                    &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; core deps (osmnx only — stays light)&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;For LLM riddles, run &lt;a href="https://ollama.com" rel="nofollow noopener noreferrer"&gt;Ollama&lt;/a&gt; separately and set
&lt;code&gt;TOUCH_GRASS_MODEL&lt;/code&gt; (e.g. &lt;code&gt;qwen3:4b&lt;/code&gt;). The daemon holds the weights; this
package only talks HTTP to localhost — no heavy deps, no model in RAM.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Usage&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;uv run touch-grass demo                       &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; offline sim, synthetic city&lt;/span&gt;
uv run touch-grass derive &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;Portland, Oregon&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt; --minutes 45&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;derive&lt;/code&gt; downloads the walk graph + POIs once, caches them under
&lt;code&gt;~/.cache/touch-grass/&lt;/code&gt;, then works fully offline.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How it works&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Module&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spatial.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cached OSMnx walk graph + POI tag extraction; radius from time budget&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;router.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Anti-cost routing: penalizes arterials, rewards footways/alleys + jitter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;persona.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Turns bearings/POIs into riddles; template by default, GGUF LLM via &lt;code&gt;TOUCH_GRASS_MODEL&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;state.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Geofenced waypoint state machine —&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;…&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Mahasvan/touch-grass" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;spatial.py&lt;/code&gt; — caches a pedestrian-only OSMnx walk graph + POI tags (&lt;code&gt;historic=*&lt;/code&gt;, &lt;code&gt;natural=*&lt;/code&gt;, &lt;code&gt;leisure=park&lt;/code&gt;...) locally before you go offline&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;router.py&lt;/code&gt; — the Anti-Router: Dijkstra where arterials cost up to 50× and footways/alleys are discounted, plus seeded jitter so no two dérives repeat&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;persona.py&lt;/code&gt; — turns bearings and POI metadata into riddles via local Ollama (stdlib &lt;code&gt;urllib&lt;/code&gt;, zero SDK), with a template fallback when no model is running&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;state.py&lt;/code&gt; — a 15 m geofence state machine: the LLM only wakes when you physically arrive&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Everything runs at the edge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Open-weight LLM&lt;/strong&gt;: any Ollama-pulled model (e.g. &lt;code&gt;qwen3:4b&lt;/code&gt;) served by a local Ollama daemon — &lt;code&gt;TOUCH_GRASS_MODEL&lt;/code&gt; selects it, &lt;code&gt;keep_alive: 5m&lt;/code&gt; drops weights between waypoints to save battery&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open data&lt;/strong&gt;: OpenStreetMap via OSMnx — &lt;code&gt;network_type="walk"&lt;/code&gt; graphs cached as GraphML, POIs pickled; after one fetch the app never touches the network&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No agent framework&lt;/strong&gt;: LangGraph/Smolagents were in the spec, but a 4-module pipeline didn't need them — plain functions won&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Built with &lt;code&gt;uv&lt;/code&gt;, testable headless (&lt;code&gt;uv run pytest&lt;/code&gt; — no network, no model required).&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;The entire premise — &lt;em&gt;data sovereignty for where your feet go&lt;/em&gt; — only works because the stack is open. Google Maps can't power an app whose selling point is that no one logs your route. OSM gave me the graph, open-weight models gave me the voice, and Ollama gave me inference that works in airplane mode. A closed API would have made this project self-defeating: the agent's value is that it knows where you walked and &lt;em&gt;tells no one&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;

&lt;p&gt;Built with Devin (SWE-2). Notable: the agent hit two real bugs on first live run — geopandas demanding pyarrow for a parquet cache (fixed by switching to stdlib pickle), and &lt;code&gt;ox.nearest_nodes&lt;/code&gt; requiring scikit-learn on unprojected graphs (replaced with a 3-line equirectangular scan). Both fixes made the dependency tree &lt;em&gt;lighter&lt;/em&gt; — staying honest to "offline-first" meant fewer deps, not more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Best use of open-weight models / local inference&lt;/li&gt;
&lt;li&gt;Best offline-first / privacy-preserving project&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
  </channel>
</rss>
