<?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: Girish</title>
    <description>The latest articles on DEV Community by Girish (@girishjsengineer).</description>
    <link>https://dev.to/girishjsengineer</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%2F3834386%2F393b3e14-12d6-4967-9727-7d7592134fe4.png</url>
      <title>DEV Community: Girish</title>
      <link>https://dev.to/girishjsengineer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/girishjsengineer"/>
    <language>en</language>
    <item>
      <title>How I Built an AI-Powered Job Search Copilot with OpenClaw (and Made It Reliable)</title>
      <dc:creator>Girish</dc:creator>
      <pubDate>Thu, 19 Mar 2026 23:19:03 +0000</pubDate>
      <link>https://dev.to/girishjsengineer/how-i-built-an-ai-powered-job-search-copilot-with-openclaw-and-made-it-reliable-4cdl</link>
      <guid>https://dev.to/girishjsengineer/how-i-built-an-ai-powered-job-search-copilot-with-openclaw-and-made-it-reliable-4cdl</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;I went from “why am I seeing the same jobs again?” to a production-grade job search system with dedupe, queue governance, and a dashboard I can actually operate daily.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why I Built This
&lt;/h2&gt;

&lt;p&gt;Like many people job hunting in tech, I ran into four recurring issues:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Repeated listings&lt;/strong&gt; kept appearing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No reliable state tracking&lt;/strong&gt; for approve/apply/reject.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scattered visibility&lt;/strong&gt; across scripts, logs, and chat alerts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fragile operations&lt;/strong&gt; where one bug could spam alerts or hide real progress.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I wanted a system that could automate aggressively while keeping key decisions under manual control.&lt;/p&gt;

&lt;p&gt;For context: &lt;strong&gt;OpenClaw&lt;/strong&gt; is an open-source agent orchestration framework for building automation workflows across tools, scripts, and messaging surfaces.&lt;/p&gt;




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

&lt;p&gt;I stopped thinking of this as “a script” and started treating it as a product with clear components.&lt;/p&gt;

&lt;h3&gt;
  
  
  1) Job ingestion + filtering pipeline
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Pull jobs&lt;/li&gt;
&lt;li&gt;Match against resume/profile criteria&lt;/li&gt;
&lt;li&gt;Queue into status model:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pending_approval&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;approved&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;applied&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rejected&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  2) Alerting that doesn’t spam
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Canonical dedupe on normalized &lt;code&gt;link&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Skip already-sent items&lt;/li&gt;
&lt;li&gt;Equal-sized batching for outbound alert messages&lt;/li&gt;
&lt;li&gt;Cleanup/rotation of pending payloads after successful send&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3) A real dashboard in Second Brain
&lt;/h3&gt;

&lt;p&gt;I built a dedicated &lt;strong&gt;Job Search tab&lt;/strong&gt; in a custom Second Brain app (&lt;strong&gt;React + Vite frontend, Bun backend&lt;/strong&gt;; grounded in the "second brain" idea of externalized operational memory) that shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;queue metrics,&lt;/li&gt;
&lt;li&gt;editable search rules,&lt;/li&gt;
&lt;li&gt;pipeline freshness timestamps,&lt;/li&gt;
&lt;li&gt;pending/approved/applied/rejected sections,&lt;/li&gt;
&lt;li&gt;conversion funnel,&lt;/li&gt;
&lt;li&gt;recent run artifacts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4) Service reliability
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Always-on runtime with macOS &lt;code&gt;launchd&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Fixed process drift with &lt;strong&gt;port pinning to 3000&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Added &lt;code&gt;stop.sh&lt;/code&gt; and &lt;code&gt;status.sh&lt;/code&gt; for operational control&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5) Semantic search support
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Enabled Gemini embeddings&lt;/li&gt;
&lt;li&gt;Added reindex flow&lt;/li&gt;
&lt;li&gt;Implemented graceful fallback to keyword search when key is unavailable&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6) Component map (what each part does)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Collector&lt;/strong&gt;: ingests source listings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Matcher&lt;/strong&gt;: scores relevance vs profile&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dedupe guard&lt;/strong&gt;: prevents repeated listings by canonical link&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batcher&lt;/strong&gt;: sends alerts in balanced chunks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Queue orchestrator&lt;/strong&gt;: controls status transitions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Policy engine&lt;/strong&gt;: rule-driven filtering/tuning&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Action layer&lt;/strong&gt;: approve/reject controls in UI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability layer&lt;/strong&gt;: timestamps, funnel, run artifacts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search layer&lt;/strong&gt;: keyword + semantic retrieval&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime supervisor&lt;/strong&gt;: always-on stability and process hygiene&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Architecture (High-Level)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Job Sources
  -&amp;gt; job_market_intelligence_bot
  -&amp;gt; dedupe + match + batch
  -&amp;gt; queue/jobs_queue.json
  -&amp;gt; Second Brain API (/api/job-search-progress)
  -&amp;gt; Job Search UI tab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The split matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pipeline layer&lt;/strong&gt; handles ingestion and state,&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UI layer&lt;/strong&gt; handles observability and decisioning.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Key Implementation Details
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Queue model first, UI second
&lt;/h3&gt;

&lt;p&gt;One of the most important decisions was making queue state explicit and authoritative.&lt;br&gt;
Without that, dashboards lie and actions become risky.&lt;/p&gt;
&lt;h3&gt;
  
  
  Dedupe by canonical identity
&lt;/h3&gt;

&lt;p&gt;I normalized links and deduped against historical alerts. This alone eliminated repeat-notification noise.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;normalizeLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// common LinkedIn/Indeed/job-board tracking params&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utm_source&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utm_medium&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utm_campaign&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ref&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&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;p&gt;So &lt;code&gt;job.com/123?utm_source=linkedin&lt;/code&gt; and &lt;code&gt;job.com/123&lt;/code&gt; collapse to one identity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Human-in-the-loop actions
&lt;/h3&gt;

&lt;p&gt;Pending jobs can be approved/rejected, and approved jobs can be re-rejected when needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Freshness timestamps
&lt;/h3&gt;

&lt;p&gt;I surfaced update timestamps for queue/new-matches/outbox/runs so stale data is obvious immediately.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problems I Hit (and Fixes)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Duplicate jobs were sent repeatedly
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; send path consumed pending data without strict dedupe checks.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; dedupe by normalized link + skip sent + clear/rotate pending state.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Measured impact:&lt;/strong&gt; in one cleanup run, &lt;strong&gt;222 duplicate already-sent alerts&lt;/strong&gt; were removed/skipped.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dashboard loaded as blank screen
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; HTML shell served, but bundled assets were not.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; serve Vite &lt;code&gt;dist/index.html&lt;/code&gt; and &lt;code&gt;dist/assets/*&lt;/code&gt; correctly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Metrics showed zeros in always-on mode
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; wrong workspace context in daemon environment.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; export explicit &lt;code&gt;WORKSPACE&lt;/code&gt; in service runner.&lt;/p&gt;

&lt;h3&gt;
  
  
  Semantic search key errors in UI
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; embeddings key not available at runtime.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; configure &lt;code&gt;.env&lt;/code&gt;, restart service, reindex cache, add keyword fallback UX.&lt;/p&gt;




&lt;h2&gt;
  
  
  UX Improvements That Actually Helped
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Collapsible queue sections&lt;/li&gt;
&lt;li&gt;Top-5 with “show more”&lt;/li&gt;
&lt;li&gt;Fit-score color chips&lt;/li&gt;
&lt;li&gt;Relative timestamps (“12m ago”)&lt;/li&gt;
&lt;li&gt;Sticky section headers&lt;/li&gt;
&lt;li&gt;Better rules-form alignment and spacing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Small improvements, big operator-speed gain.&lt;/p&gt;

&lt;h2&gt;
  
  
  The daily operating loop (what changed behavior)
&lt;/h2&gt;

&lt;p&gt;Before: browse jobs, lose context, repeat tomorrow.&lt;/p&gt;

&lt;p&gt;Now:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check freshness + queue counters&lt;/li&gt;
&lt;li&gt;Process pending approvals&lt;/li&gt;
&lt;li&gt;Re-check approved jobs for quality&lt;/li&gt;
&lt;li&gt;Tune rules based on noise patterns&lt;/li&gt;
&lt;li&gt;Review funnel and recent runs&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That loop made the system compounding: each day’s actions improve next day’s signal.&lt;/p&gt;




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

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Automation without observability is brittle.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Canonical state design beats post-hoc patching.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Service environment parity is critical (shell vs daemon).&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Graceful degradation is mandatory for UX trust.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UI polish improves decision quality, not just aesthetics.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  What’s Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Batch actions for approved jobs&lt;/li&gt;
&lt;li&gt;Better source confidence/ranking&lt;/li&gt;
&lt;li&gt;Saved rule presets and custom views&lt;/li&gt;
&lt;li&gt;Health checks + stale-state self-healing&lt;/li&gt;
&lt;li&gt;Enhanced analytics for conversion trends&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Repro Notes
&lt;/h2&gt;

&lt;p&gt;If you’re replicating this approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;start with queue states and dedupe key,&lt;/li&gt;
&lt;li&gt;build freshness visibility early,&lt;/li&gt;
&lt;li&gt;add always-on service controls before scaling sources,&lt;/li&gt;
&lt;li&gt;keep secrets in &lt;code&gt;.env&lt;/code&gt; and ensure &lt;code&gt;.env&lt;/code&gt; is gitignored,&lt;/li&gt;
&lt;li&gt;and only then optimize UX.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That sequence saved me a lot of rework.&lt;/p&gt;




&lt;h3&gt;
  
  
  Final take
&lt;/h3&gt;

&lt;p&gt;If your current job search feels noisy and untrackable, don’t start by adding more scripts. Start by designing the operating model: states, dedupe identity, and visibility. Once those are solid, automation becomes trustworthy — and compounding.&lt;/p&gt;

&lt;p&gt;Are you building something similar or want the OpenClaw implementation details? Drop a comment or reach out — I’m happy to share the practical playbook.&lt;/p&gt;

</description>
      <category>openclaw</category>
      <category>jobsearch</category>
      <category>automation</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
