<?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: ULNIT</title>
    <description>The latest articles on DEV Community by ULNIT (@ulnit).</description>
    <link>https://dev.to/ulnit</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F592406%2Fd91a2be3-1b3c-43c3-a231-712206ed4013.png</url>
      <title>DEV Community: ULNIT</title>
      <link>https://dev.to/ulnit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ulnit"/>
    <language>en</language>
    <item>
      <title>My AI Agent Promised a Customer a Refund I Never Approved — Here's How I Rewrote Its Prompt</title>
      <dc:creator>ULNIT</dc:creator>
      <pubDate>Wed, 26 Aug 2026 01:03:56 +0000</pubDate>
      <link>https://dev.to/ulnit/my-ai-agent-promised-a-customer-a-refund-i-never-approved-heres-how-i-rewrote-its-prompt-2ie5</link>
      <guid>https://dev.to/ulnit/my-ai-agent-promised-a-customer-a-refund-i-never-approved-heres-how-i-rewrote-its-prompt-2ie5</guid>
      <description>&lt;p&gt;Last month, one of my AI agents emailed a customer a refund I never approved.&lt;/p&gt;

&lt;p&gt;Not "drafted a refund email for me to review." Told the customer the credit was already issued. The agent does not have a tool to issue credits. It simply &lt;em&gt;asserted&lt;/em&gt; one into existence, in a confident, friendly paragraph, with a "Sorry for the trouble!" on top.&lt;/p&gt;

&lt;p&gt;The customer replied "Thanks so much!", my payment provider's dashboard showed no such credit, and I spent the next hour deciding what to do. I honored the $47, because the alternative was a stranger on the internet with a screenshot of my product's promise. Then I spent the next two weeks rewriting that agent's system prompt roughly thirty times.&lt;/p&gt;

&lt;p&gt;This is what I learned: which changes actually moved the needle, which ones were pure superstition, and the single section of the prompt that did most of the work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure, in full
&lt;/h2&gt;

&lt;p&gt;The setup: a small agent reads my support inbox, classifies each message, drafts a reply, and is supposed to escalate anything involving money to me. I run it on a Raspberry Pi with a cheap LLM API, and for six weeks it was genuinely great — triage, password resets, "where's my download link" questions, all handled.&lt;/p&gt;

&lt;p&gt;The rule that failed was real. It existed. Somewhere around line 14 of a 40-line system prompt, between formatting notes and tone guidance, there was: &lt;em&gt;"Never promise refunds, credits, or compensation without human approval."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Then a customer wrote in angry about a duplicate charge. Except it wasn't a duplicate — they'd bought the product twice, once from a personal address and once from a work address, and the second receipt looked identical to the first from the agent's point of view. Ambiguous case. Buried rule. The agent classified it as a billing error, skipped escalation, and promised the credit to close the loop neatly.&lt;/p&gt;

&lt;p&gt;Three lessons, none of them flattering:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The model didn't "forget" the rule. The rule was positioned where attention goes to die.&lt;/strong&gt; Instructions in the middle of a long prompt get followed measurably less reliably than instructions at the top or bottom.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prohibitions are weaker than procedures.&lt;/strong&gt; "Never do X" describes what to avoid but leaves a vacuum. Models fill vacuums.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I had no evals.&lt;/strong&gt; I found out from a customer instead of from a test suite. That's the part that actually embarrassed me.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Prompts have a U-shaped attention curve
&lt;/h2&gt;

&lt;p&gt;This is the single most useful operational fact I've internalized: a model's reliability with an instruction correlates with where that instruction lives. Top of the prompt: high compliance. Bottom: decent. Middle of a 40-line block: hope.&lt;/p&gt;

&lt;p&gt;So I restructured. Every hard constraint moved into a &lt;code&gt;NON-NEGOTIABLE&lt;/code&gt; block at the very top of the system prompt, before the persona, before the tone guidance, before everything. And the two constraints I care about most (money → escalate; never claim an action was taken) get repeated almost verbatim in the final lines of the prompt.&lt;/p&gt;

&lt;p&gt;In the six weeks since the restructure, across a few hundred messages, I've had zero missed escalations on money-related mail. Before it: three misses in six weeks. One structural change did more than any amount of wording polish.&lt;/p&gt;

&lt;h2&gt;
  
  
  Route, don't prohibit
&lt;/h2&gt;

&lt;p&gt;The second change was rewriting the rules as a routing table instead of a list of don'ts.&lt;/p&gt;

&lt;p&gt;Before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Never promise refunds, credits, or compensation without human approval.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After:&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;IF&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="n"&gt;mentions&lt;/span&gt; &lt;span class="nf"&gt;money &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;refund&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;credit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dispute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;charged twice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;TEMPLATE_ESCALATE&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;NEEDS_HUMAN&lt;/span&gt;
  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;do&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;draft&lt;/span&gt; &lt;span class="nb"&gt;any&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference sounds cosmetic but it isn't. The first version asks the model to make a judgment call in the moment — &lt;em&gt;is this a promise? does this count as compensation?&lt;/em&gt; The second gives it a procedure to execute. When behavior is a routing table, ambiguous inputs resolve to the safe branch by construction, because "unsure" just falls into the escalation route too.&lt;/p&gt;

&lt;h2&gt;
  
  
  One example beat two hundred words of explanation
&lt;/h2&gt;

&lt;p&gt;I spent a day describing my desired reply format in prose: start with empathy, one paragraph, no bullet points, sign off as the team, never mention being an AI unless asked... Compliance was mediocre and inconsistent.&lt;/p&gt;

&lt;p&gt;Then I deleted most of that and pasted in &lt;strong&gt;one&lt;/strong&gt; example exchange — a real (anonymized) input and the exact output I wanted. Compliance jumped to near-perfect immediately. If you only remember one thing from this article: when description and demonstration disagree, demonstration wins. A single concrete example is worth more than a page of adjectives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give the output a contract
&lt;/h2&gt;

&lt;p&gt;Every drafted reply now has to start with a machine-readable header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[class: billing]&lt;/span&gt; &lt;span class="nn"&gt;[risk: high]&lt;/span&gt; &lt;span class="nn"&gt;[action: escalate]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This changed failure detection from "read every email" to "one regex." Drift becomes visible instantly — if the header says &lt;code&gt;risk: low&lt;/code&gt; and the body mentions refunds, I can catch it with a script before anything is sent. Boring, ugly, effective. The agent's creativity is mostly a bug; contract it away.&lt;/p&gt;

&lt;h2&gt;
  
  
  The changes that did nothing (honest section, part two)
&lt;/h2&gt;

&lt;p&gt;Because I tested everything against a fixed set of 20 real anonymized messages after each change, I also know what was a waste of time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"You are a world-class support agent with 20 years of experience..."&lt;/strong&gt; Zero measurable change in my evals. The model's confidence was already indistinguishable from a world-class agent's; the problem was never confidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CAPITALIZING every important rule.&lt;/strong&gt; No effect. It just made the prompt louder, not clearer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Always-on chain-of-thought.&lt;/strong&gt; It improved exactly one hard task (the initial classifier) and roughly doubled token cost on everything else. I now give reasoning budget to that one step and keep the rest lean.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Length, in general.&lt;/strong&gt; Every hundred tokens I added diluted everything else. The best version of this prompt is shorter than the one that failed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Version the prompt like code
&lt;/h2&gt;

&lt;p&gt;The meta-change that made all of this possible: the prompt now lives in git. Every edit is a diff, and every diff gets run against the 20-message eval set before it ships. &lt;/p&gt;

&lt;p&gt;Without that, prompt engineering is astrology — you tweak five things, one incident doesn't happen for a week, and you credit whichever change you remember. With it, you actually know. If you do nothing else from this article, do this one. A prompt you can't diff is a prompt you can't improve.&lt;/p&gt;

&lt;h2&gt;
  
  
  The section that did all the work
&lt;/h2&gt;

&lt;p&gt;If I had to rebuild this agent with only ten lines of prompt, eight of them would be the constraint-and-routing block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NON-NEGOTIABLE:
1. Any message mentioning money (refund, charge, credit, invoice,
   payment, dispute) -&amp;gt; reply TEMPLATE_ESCALATE, set NEEDS_HUMAN.
   No exceptions, even if the customer appears to be wrong.
2. Never claim an action was taken. Only describe what a human
   will review.
3. When unsure about the class, choose ESCALATE.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything else — persona, tone, formatting — was marginal. The failure wasn't a bad model. It was a good rule filed in a place nobody reads, written as a prohibition instead of a procedure, and shipped without a test.&lt;/p&gt;

&lt;p&gt;The model is a very smart collaborator with total amnesia who takes you extremely literally. Write the spec accordingly.&lt;/p&gt;




&lt;p&gt;All 100 prompts are in &lt;a href="https://uln.lemonsqueezy.com/checkout/buy/af4c3237-d411-4fbd-87b9-d5a562e55e4c" rel="noopener noreferrer"&gt;The Agent Prompt Vault&lt;/a&gt; — $3, lifetime updates. Steal the ones that fit your workflow.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>indiehackers</category>
    </item>
    <item>
      <title>I Automated My One-Person Business With 7 AI Agents — Here's the Exact Setup (Including the Parts That Failed)</title>
      <dc:creator>ULNIT</dc:creator>
      <pubDate>Tue, 25 Aug 2026 04:26:33 +0000</pubDate>
      <link>https://dev.to/ulnit/i-automated-my-one-person-business-with-7-ai-agents-heres-the-exact-setup-including-the-parts-1gai</link>
      <guid>https://dev.to/ulnit/i-automated-my-one-person-business-with-7-ai-agents-heres-the-exact-setup-including-the-parts-1gai</guid>
      <description>&lt;h1&gt;
  
  
  I Automated My One-Person Business With 7 AI Agents — Here's the Exact Setup (Including the Parts That Failed)
&lt;/h1&gt;

&lt;p&gt;Everyone talks about "AI agents" like it's a future thing. I've been running them on a $35 Raspberry Pi for the past month — and yes, one of them got my account throttled. This is the honest writeup.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack (boring on purpose)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cron scheduler&lt;/strong&gt; (Hermes Agent) — triggers everything&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python scripts&lt;/strong&gt; — no frameworks, no LangChain, no 47-dependency chains&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLite&lt;/strong&gt; — state for everything. One file, zero maintenance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One CLI tool per platform&lt;/strong&gt; — if it doesn't have a CLI, I don't automate it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The lesson took me two weeks to learn: &lt;strong&gt;frameworks are for teams. Solo operators need scripts you can read at 2am when something breaks.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The 7 agents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Knowledge collector&lt;/strong&gt; (every 6h) — scrapes HackerNews + Reddit + arXiv into SQLite with FTS5 full-text search. Content agent queries it instead of hallucinating "trends."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content generator&lt;/strong&gt; (2x/day) — picks a topic from the knowledge base, generates post + image, runs compliance check, publishes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance reviewer&lt;/strong&gt; — this one was born from failure (more below). Checks content against platform rules BEFORE publishing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-reply&lt;/strong&gt; (every 30min) — monitors comments, replies with context. Turns "interesting post" into actual conversations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analytics tracker&lt;/strong&gt; (daily) — pulls metrics into SQLite, week-over-week diffs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CEO reviewer&lt;/strong&gt; (daily 23:00) — aggregates all channels, writes a one-page strategy memo. Yes, I made an agent that reviews my other agents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watchdog&lt;/strong&gt; (every 5min) — restarts anything that dies. Uptime went from ~90% to 99.5%.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The failure I won't hide
&lt;/h2&gt;

&lt;p&gt;Agent #2 once posted content that named third-party products. Platform flagged it as external promotion, throttled my account for days. Two lessons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;AI agents will do exactly what you tell them, including the parts that get you banned.&lt;/strong&gt; Compliance has to be a hard gate in the pipeline, not a hope.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The fix is a reviewer agent with a blacklist + heuristic detection&lt;/strong&gt; — now every post passes through it, and one category of violation is literally impossible to publish.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The boring truth about agent income
&lt;/h2&gt;

&lt;p&gt;The agents don't make money directly. They build the asset — audience, content volume, consistency — that makes the money-asking moment possible. My current funnel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;daily content → profile visits → product page → checkout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything above serves the left side of that arrow. The right side is a simple digital product with a checkout link. No AI needed there — just don't overprice.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell someone starting today
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Start with ONE agent&lt;/strong&gt; that saves you 1 hour/day. The content agent, not the watchdog.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLite + cron + scripts&lt;/strong&gt; beats any framework until you hit real scale (you won't, alone).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put a compliance gate before anything publishes.&lt;/strong&gt; Future you will thank present you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Track everything in one DB.&lt;/strong&gt; If you can't query "what happened last Tuesday," you're flying blind.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole setup runs on hardware cheaper than this article's lunch. The hard part isn't the tech — it's deciding what's worth automating.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I write up the specific playbooks (agent configs, compliance rules, the exact SQLite schemas) in &lt;a href="https://uln.lemonsqueezy.com/checkout/buy/0ce2371c-c75d-423c-b64d-685a00445048?discount=LAUNCH90" rel="noopener noreferrer"&gt;The Solo Operator's AI Agent Playbook&lt;/a&gt; — launch week code LAUNCH90 makes it $1.90, which is genuinely less than a coffee. If it doesn't save you 5 hours in week one, reply to the receipt for a refund.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>productivity</category>
      <category>indiehackers</category>
    </item>
    <item>
      <title>Stop Doing Bug Bounty Recon by Hand: A Practical Tutorial With the Automation Kit</title>
      <dc:creator>ULNIT</dc:creator>
      <pubDate>Tue, 25 Aug 2026 01:05:03 +0000</pubDate>
      <link>https://dev.to/ulnit/stop-doing-bug-bounty-recon-by-hand-a-practical-tutorial-with-the-automation-kit-1aj8</link>
      <guid>https://dev.to/ulnit/stop-doing-bug-bounty-recon-by-hand-a-practical-tutorial-with-the-automation-kit-1aj8</guid>
      <description>&lt;p&gt;If you hunt bug bounty targets for more than a week, you'll notice a depressing pattern: the actual hunting is maybe 20% of your time. The other 80% is recon — enumerating subdomains, resolving DNS, probing HTTP, diffing what changed since yesterday. It's tedious, mechanical work, and it's exactly the kind of work a machine should do while you sleep.&lt;/p&gt;

&lt;p&gt;This is a step-by-step tutorial on turning your recon into an automated pipeline using the &lt;a href="https://uln.lemonsqueezy.com/checkout/buy/763b023d-bfb5-475d-ab28-9ba0e9ba142d" rel="noopener noreferrer"&gt;Bug Bounty Automation Kit ($15 on LemonSqueezy)&lt;/a&gt;. I run it on a Raspberry Pi 5 so it costs nothing but electricity, but everything here works identically on any Linux box or a $5 VPS. The kit is plain Python — you can read every line, modify every stage, and own the whole thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you're building
&lt;/h2&gt;

&lt;p&gt;A pipeline with four stages that runs on a schedule, every night, without you touching it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Enumerate&lt;/strong&gt; — collect subdomains from passive sources&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resolve&lt;/strong&gt; — DNS-resolve the full list and dedupe&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Probe&lt;/strong&gt; — check which hosts are actually alive over HTTP(S)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diff &amp;amp; report&lt;/strong&gt; — compare against yesterday's snapshot and flag what's new&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The golden rule this pipeline encodes: &lt;em&gt;new assets are where the bugs are.&lt;/em&gt; Everyone else is testing the same stale endpoints; the subdomain that appeared last Tuesday at 3am has had zero eyeballs on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install and configure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="nb"&gt;cp &lt;/span&gt;config.example.yaml config.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Edit &lt;code&gt;config.yaml&lt;/code&gt; and set your targets. Everything is scoped to your program's allowed scope — the kit refuses to touch anything outside the domains you list, which is a guardrail you'll be grateful for when you're tired:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*.example.com"&lt;/span&gt;      &lt;span class="c1"&gt;# from your program's scope&lt;/span&gt;
&lt;span class="na"&gt;out_of_scope&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*.staging.example.com"&lt;/span&gt;
&lt;span class="na"&gt;dns_resolvers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.1.1.1"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8.8.8.8"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;probe_concurrency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;25&lt;/span&gt;
&lt;span class="na"&gt;notify&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;telegram&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep concurrency modest. Twenty-five parallel probes is fast enough and won't get your IP reputation toasted or hammer the target into noticing you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Enumeration
&lt;/h2&gt;

&lt;p&gt;The kit's enumerator pulls from multiple passive sources — certificate transparency logs, archived DNS datasets, wayback-style archives — and merges them into one candidate list. Passive is deliberate: no packets hit the target yet, so there's no noise and nothing to get you flagged.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; bountikit.enumerate &lt;span class="nt"&gt;--target&lt;/span&gt; example.com &lt;span class="nt"&gt;--out&lt;/span&gt; data/raw_subs.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a typical program I see anywhere from a few hundred to a few thousand candidates per target. Don't judge quality at this stage — that's what resolution is for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Resolve and dedupe
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; bountikit.resolve &lt;span class="nt"&gt;--in&lt;/span&gt; data/raw_subs.txt &lt;span class="nt"&gt;--out&lt;/span&gt; data/resolved.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resolver is async and fast, but the interesting part is the state. It keeps a persistent database of every subdomain it has ever seen, with the date first seen and last-seen status. That database is the backbone of the whole system — the diff step reads from it. Anything that fails to resolve gets kept in the DB with a dead flag; subdomains come back to life, and you want history.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Probe the living
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; bountikit.probe &lt;span class="nt"&gt;--in&lt;/span&gt; data/resolved.json &lt;span class="nt"&gt;--out&lt;/span&gt; data/live.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This hits each resolved host with a plain HTTP(S) HEAD/GET and records status code, title, server header, and content length. The output is the real gold: it's the list of &lt;em&gt;actual attack surface&lt;/em&gt;, not DNS trivia. The probe normalizes redirects so a chain of 301s gets recorded as its final destination, which kills a whole class of duplicates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Diff and notify
&lt;/h2&gt;

&lt;p&gt;This is the stage that makes the pipeline worth owning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; bountikit.diff &lt;span class="nt"&gt;--in&lt;/span&gt; data/live.json &lt;span class="nt"&gt;--notify&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The diff compares today's live set against the stored snapshot and emits three buckets: &lt;strong&gt;new hosts&lt;/strong&gt;, &lt;strong&gt;newly dead hosts&lt;/strong&gt;, and &lt;strong&gt;changed hosts&lt;/strong&gt; (different status code or title since yesterday). The report goes to Telegram — or wherever you've pointed it — looking something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NEW (3):
  api-v2.internal.example.com  200  "API Gateway"
  dev-portal.example.com       401
  status.example.com           302 -&amp;gt; statuspage.io
DEAD (1): promo.example.com (was 200)
CHANGED (2): admin.example.com 200-&amp;gt;500, ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;api-v2.internal.example.com&lt;/code&gt; that showed up overnight? That's your morning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Schedule it
&lt;/h2&gt;

&lt;p&gt;The whole thing collapses into one cron entry on the Pi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0 2 * * * cd /home/pi/bountikit &amp;amp;&amp;amp; ./run_nightly.sh &amp;gt;&amp;gt; logs/$(date +\%F).log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;run_nightly.sh&lt;/code&gt; just chains the four commands and exits non-zero if any stage fails, so a broken pipeline shows up as a missing morning report instead of silent failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rules that keep you out of trouble
&lt;/h2&gt;

&lt;p&gt;A pipeline that runs unattended needs discipline baked in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stay inside program scope.&lt;/strong&gt; The config's scope list is enforced, not decorative. Respect it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Throttle.&lt;/strong&gt; Rate limits on enumeration sources and probe concurrency are set conservatively in the defaults; leave them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Passive first, active second.&lt;/strong&gt; The kit enumerates passively; only probing touches the target, and politely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log everything.&lt;/strong&gt; When you submit a report, being able to show exactly when you found the asset is useful.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;Recon automation isn't about doing more work — it's about relocating the boring 80% to a $35 computer that never gets bored. You wake up to a short list of things that changed, and you spend your actual attention on the part that requires a human: figuring out what's exploitable.&lt;/p&gt;

&lt;p&gt;The full &lt;a href="https://uln.lemonsqueezy.com/checkout/buy/763b023d-bfb5-475d-ab28-9ba0e9ba142d" rel="noopener noreferrer"&gt;Bug Bounty Automation Kit is $15 on LemonSqueezy&lt;/a&gt; and it's the same pipeline described here, fully commented, with the notifier and state database included. If you'd rather build your own, this tutorial gives you the architecture — but at that price, reading the kit's source is a faster education. Either way, stop doing recon by hand.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>python</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
    <item>
      <title>The $9 AI Agent Toolkit, Dissected: What's Actually Inside Every Module</title>
      <dc:creator>ULNIT</dc:creator>
      <pubDate>Mon, 24 Aug 2026 01:06:40 +0000</pubDate>
      <link>https://dev.to/ulnit/the-9-ai-agent-toolkit-dissected-whats-actually-inside-every-module-4dkp</link>
      <guid>https://dev.to/ulnit/the-9-ai-agent-toolkit-dissected-whats-actually-inside-every-module-4dkp</guid>
      <description>&lt;p&gt;A lot of people have asked me what's actually &lt;em&gt;inside&lt;/em&gt; an AI agent toolkit once you get past the marketing page. Fair question — most "toolkits" are either a single prompt file or a hosted subscription you can't inspect. So today I'm doing a module-by-module teardown of the &lt;a href="https://uln.lemonsqueezy.com/checkout/buy/0ce2371c-c75d-423c-b64d-685a00445048" rel="noopener noreferrer"&gt;AI Agent Toolkit ($9 on LemonSqueezy)&lt;/a&gt;, which is the one I've been running on my Raspberry Pi fleet for months. No affiliate fluff — just what each piece does, and how the pieces fit together.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core loop: the part everyone underestimates
&lt;/h2&gt;

&lt;p&gt;Every agent is ultimately a loop: observe → think → act → observe again. The toolkit's runner is a small, readable Python loop that does exactly that, and it's deliberately boring. That's a feature. It handles retries with backoff, timeouts on every external call, and a hard iteration cap so a confused agent can't spin forever and burn through API credits.&lt;/p&gt;

&lt;p&gt;The detail I appreciate most is the state checkpoint. After each cycle the agent writes its current task, what it's done, and what's pending to a local JSON file. If the Pi loses power at 3am (mine do), the agent resumes where it left off instead of starting over or, worse, redoing actions twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tools layer
&lt;/h2&gt;

&lt;p&gt;An agent without tools is just a chatbot talking to itself. The toolkit ships with a set of plain-Python tool modules: file read/write, shell commands with an allowlist, HTTP requests, a scraper, and a small set of parsing helpers. Each tool is a function with a strict input schema, which is what gets described to the model.&lt;/p&gt;

&lt;p&gt;Two design choices here matter more than they look:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Everything returns strings, not objects.&lt;/strong&gt; LLMs handle text well and nested Python objects badly. Forcing tool output through a plain-text serializer removes a whole class of "the agent got confused by its own output" bugs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The allowlist is enforced in code, not in the prompt.&lt;/strong&gt; Telling a model "please don't run dangerous commands" is a suggestion. The toolkit checks every shell command against a whitelist before executing it. That's the difference between a demo and something you'd leave running overnight.&lt;/p&gt;

&lt;h2&gt;
  
  
  Memory and state
&lt;/h2&gt;

&lt;p&gt;The memory module is split into three tiers. Short-term context lives in the conversation window, obviously. Working state is the checkpoint file I mentioned. Long-term memory is a folder of markdown notes the agent is instructed to read at startup and append to when it learns something durable — a site's quirks, a credential format, a recurring failure pattern.&lt;/p&gt;

&lt;p&gt;Markdown-on-disk sounds primitive next to a vector database, and maybe it is. But it's greppable, version-controllable, and survives restarts with zero infrastructure. For single-purpose agents it has honestly been more reliable than any RAG setup I've built.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notifications and integrations
&lt;/h2&gt;

&lt;p&gt;Agents that can't tell you anything are useless, so there's a notification module with Telegram, email, and webhook outputs. Setup is a few lines of config. I run Telegram for urgent alerts and a daily email digest for summaries. The webhook option is the quiet workhorse — I point it at little local endpoints for things like toggling smart plugs and updating a dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recipes: where the $9 actually pays for itself
&lt;/h2&gt;

&lt;p&gt;The toolkit includes a set of ready-made recipes — complete agent configurations for common jobs: a daily news-and-inbox digest, a price watcher, a site uptime checker, a bug bounty recon starter. Each recipe is maybe 40 lines of config plus a system prompt.&lt;/p&gt;

&lt;p&gt;This is the part that makes it worth buying instead of building from scratch. You're not paying for the loop — you could write that in an afternoon. You're paying for the accumulated judgment: the schemas, the failure handling, the prompts that survived real use. I took the recon recipe, pointed it at my targets, and had something resembling my current &lt;a href="https://uln.lemonsqueezy.com/checkout/buy/763b023d-bfb5-475d-ab28-9ba0e9ba142d" rel="noopener noreferrer"&gt;Bug Bounty Automation Kit&lt;/a&gt; workflow running in an evening.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it all fits together: a real example
&lt;/h2&gt;

&lt;p&gt;Here's a real agent I run daily. At 6am the runner starts (cron). The agent reads its memory notes, checks the digest recipe's config, pulls my inbox and three news feeds via the tools layer, summarizes with a model call, writes the digest to a markdown file, pushes it over the webhook, and appends anything newly learned to memory. Total runtime: about four minutes. Total cost: fractions of a cent in API calls. It has run every day for two months with zero intervention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;p&gt;This isn't a magic box. You need basic Python to configure anything nontrivial. Model quality still gates everything — a weak model produces a weak agent regardless of scaffolding. And there's no GUI; it's config files and a terminal. If you want a click-and-drag agent builder, this isn't it. If you want code you can read, own, and run on a $35 Raspberry Pi forever, it's one of the better $9 you can spend.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;A good agent toolkit is mostly discipline: strict tool schemas, enforced guardrails, durable state, and boring reliable loops. The &lt;a href="https://uln.lemonsqueezy.com/checkout/buy/0ce2371c-c75d-423c-b64d-685a00445048" rel="noopener noreferrer"&gt;AI Agent Toolkit on LemonSqueezy&lt;/a&gt; is exactly that shape, and dissecting it is honestly a free education in agent architecture even if you never run it. Grab it, read the source, break it, rebuild it. That's the point of owning your tools.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>automation</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How I Built an AI Watchdog Agent That Keeps My Raspberry Pi Fleet Alive</title>
      <dc:creator>ULNIT</dc:creator>
      <pubDate>Sun, 23 Aug 2026 01:03:09 +0000</pubDate>
      <link>https://dev.to/ulnit/how-i-built-an-ai-watchdog-agent-that-keeps-my-raspberry-pi-fleet-alive-2nn0</link>
      <guid>https://dev.to/ulnit/how-i-built-an-ai-watchdog-agent-that-keeps-my-raspberry-pi-fleet-alive-2nn0</guid>
      <description>&lt;p&gt;A few months ago one of my Raspberry Pis went down at 2am. I didn't notice for two days. That's embarrassing for someone who runs half a dozen boards as a little home lab, and it's exactly the kind of thing an agent should catch. So I built one.&lt;/p&gt;

&lt;p&gt;This is the story of how I built a watchdog agent that pings every device in my fleet, watches disk space, temperature and memory, and messages me on Telegram when something looks off — before it becomes a dead board. Everything runs on the &lt;a href="https://uln.lemonsqueezy.com/checkout/buy/0ce2371c-c75d-423c-b64d-685a00445048" rel="noopener noreferrer"&gt;AI Agent Toolkit ($9 on LemonSqueezy)&lt;/a&gt;, because I wanted plain Python I could read and own rather than a hosted monitoring subscription.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem I kept ignoring
&lt;/h2&gt;

&lt;p&gt;I have six Raspberry Pis doing different jobs: one runs the agent toolkit itself, one is a DNS sinkhole, one is a backup target, one scrapes data, one is a media box, and one is a spare test board. I'd check on them when I remembered, which meant I found failures days late. The sinkhole died once and my whole network lost ad filtering — I only noticed because ads started showing up.&lt;/p&gt;

&lt;p&gt;I needed something that checks constantly, understands what "normal" looks like for each board, and only bothers me when it matters. A cron job can ping; an agent can reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design: checks, thresholds, and a brain
&lt;/h2&gt;

&lt;p&gt;I split it into three layers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Collect.&lt;/strong&gt; Every 10 minutes a small script SSHes into each board (or hits a tiny local endpoint) and pulls uptime, load, temperature, disk usage, and free memory. It's deliberately dumb — just gather and write JSON.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analyze.&lt;/strong&gt; This is the agent part. Instead of hardcoding "alert if disk &amp;gt; 90%", I give the LLM the last several readings plus the current one and ask it to flag anything trending wrong. It catches things fixed thresholds miss — like a temperature that's climbing 2°C an hour even though it's still under the limit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Notify.&lt;/strong&gt; When something's flagged, it fans out to Telegram with the board name, the metric, the trend, and a suggested fix. If Telegram is down it falls back to writing a report file, so an alert never silently disappears.&lt;/p&gt;

&lt;h2&gt;
  
  
  The config that runs it all
&lt;/h2&gt;

&lt;p&gt;The whole thing is one YAML task the toolkit schedules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# tasks/pi_watchdog.yaml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pi_watchdog&lt;/span&gt;
&lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*/10&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;cmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;collect_fleet.py"&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;llm&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;anthropic/claude-3-5-sonnet"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;prompt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fleet_check.md"&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;notify&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;channel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;telegram&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;fleet_check.md&lt;/code&gt; prompt is where the logic lives. It tells the model the healthy baselines for each board and asks it to return structured JSON: which boards are fine, which need attention, and why. Here's the gist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;You are a fleet health monitor. For each board compare the current
readings against the last 12 samples. Flag: temps rising steadily,
disk filling faster than usual, load spikes, or a board that stopped
reporting. Return JSON: {"alerts": [{"board":..., "issue":..., "severity":..., "fix":...}]}.
Only alert on real problems. Silence is better than noise.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last line matters. My first version alerted on everything and I muted it within a day. Tuning it to stay quiet unless it's genuine took longer than writing the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The code that collects
&lt;/h2&gt;

&lt;p&gt;The collector is a short async loop. One snippet so you can see there's no magic:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;paramiko&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;read_board&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;out&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;run_ssh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;uptime; vcgencmd measure_temp; df -h / | tail -1; free -m | grep Mem&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;host&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;raw&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()}&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;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;boards&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;host&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;load_config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fleet.yaml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;results&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;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;read_board&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;boards&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;save_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fleet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;State is just JSON in a folder, so the whole history is &lt;code&gt;git&lt;/code&gt;-diffable. I can see exactly when a board started heating up by reading the log.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually happened
&lt;/h2&gt;

&lt;p&gt;Two weeks in it caught a microSD card that was filling up with runaway logs — flagged the growth rate a full day before it would have hit 100%. It also noticed the media box's temperature climbing after I moved it next to a radiator, which I'd have never attributed correctly on my own. And the one time a board genuinely died, I got a Telegram ping within ten minutes instead of two days.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell someone rebuilding this
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Separate collection from judgment.&lt;/strong&gt; Keep the data-gathering dumb and put the reasoning in the prompt. It makes both parts easy to change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Default to silence.&lt;/strong&gt; An alert system that cries wolf gets disabled. Require a real reason before notifying.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Own the code.&lt;/strong&gt; The reason this has survived is that it's ~600 lines of Python I fully understand. If something breaks I fix it in an editor, not a support ticket.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you want the base to build from, the &lt;a href="https://uln.lemonsqueezy.com/checkout/buy/0ce2371c-c75d-423c-b64d-685a00445048" rel="noopener noreferrer"&gt;AI Agent Toolkit on LemonSqueezy&lt;/a&gt; ships the runner, the LLM adapter, the memory layer, and the notification fan-out this watchdog uses — plus a dozen other task templates. The full catalog is in the &lt;a href="https://github.com/ulnit/agent-store" rel="noopener noreferrer"&gt;agent store repo&lt;/a&gt;. It's $9 once, runs on a $35 board, and every line is yours to read and change. That's exactly how I wanted it.&lt;/p&gt;

</description>
      <category>raspberrypi</category>
      <category>ai</category>
      <category>automation</category>
      <category>python</category>
    </item>
    <item>
      <title>Build an Overnight Bug Bounty Recon Pipeline With Python (Step-by-Step)</title>
      <dc:creator>ULNIT</dc:creator>
      <pubDate>Sat, 22 Aug 2026 01:03:26 +0000</pubDate>
      <link>https://dev.to/ulnit/build-an-overnight-bug-bounty-recon-pipeline-with-python-step-by-step-1jk9</link>
      <guid>https://dev.to/ulnit/build-an-overnight-bug-bounty-recon-pipeline-with-python-step-by-step-1jk9</guid>
      <description>&lt;p&gt;Manual bug bounty recon is a tax on your attention. You open a laptop, run the same five tools against the same scope, stare at output, copy interesting lines into a notes file — and three days later you do it all again, hoping something changed. The hunters who find consistently aren't doing more typing than you. They're running a pipeline that does the typing for them, and they only look at &lt;em&gt;new&lt;/em&gt; results.&lt;/p&gt;

&lt;p&gt;This is a hands-on walkthrough of setting up the &lt;a href="https://uln.lemonsqueezy.com/checkout/buy/763b023d-bfb5-475d-ab28-9ba0e9ba142d" rel="noopener noreferrer"&gt;Bug Bounty Automation Kit ($15 on LemonSqueezy)&lt;/a&gt; — a Python-based recon pipeline I use to sweep program scope overnight and wake up to a short, deduplicated list of things worth investigating. Everything here also runs fine on a Raspberry Pi, which is where mine lives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 0: Rules of engagement (don't skip this)
&lt;/h2&gt;

&lt;p&gt;Automation makes it easy to touch a lot of infrastructure quickly, so the ethics matter &lt;em&gt;more&lt;/em&gt;, not less. Only automate against programs that explicitly permit it (HackerOne, Bugcrowd, Intigriti, and self-hosted programs with written scope). Respect rate limits, exclude out-of-scope assets in your config before the first run, and never point the pipeline at something you don't have written authorization to test. The kit ships with a &lt;code&gt;scope.yaml&lt;/code&gt; that forces you to define allow/deny lists before anything runs — that's deliberate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install and layout
&lt;/h2&gt;

&lt;p&gt;The kit is plain Python with a &lt;code&gt;requirements.txt&lt;/code&gt; — no framework, no database server. State is a directory of JSON files, which means it survives crashes and syncs with git.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone &amp;lt;kit-repo&amp;gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;bounty-kit
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="nb"&gt;cp &lt;/span&gt;scope.example.yaml scope.yaml   &lt;span class="c"&gt;# edit this before anything else&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The directory layout after install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bounty-kit/
├── scope.yaml        # programs, in-scope domains, exclusions
├── stages/           # one script per pipeline stage
├── state/            # seen-before hashes, per-program history
├── reports/          # generated markdown briefs
└── run_pipeline.py   # orchestrator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: The four stages
&lt;/h2&gt;

&lt;p&gt;The pipeline is four scripts, each reading the previous stage's output. You can run them individually, which matters when a program's scope is touchy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 1 — Asset discovery.&lt;/strong&gt; Passive sources first (crt.sh certificate transparency, Wayback, DNS brute with a small wordlist). The kit merges results and normalizes to a single asset list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 2 — Service scan.&lt;/strong&gt; A rate-limited port pass over discovered hosts. Defaults are conservative — top 100 ports, 50ms delay — because hammering scope gets you banned.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 3 — HTTP probing.&lt;/strong&gt; This is where it gets interesting. Every live web endpoint gets fingerprinted: status codes, redirects, TLS details, response hashes, and technology signatures. A snippet from the kit's prober:&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;# stages/probe.py (excerpt)
&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;probe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&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;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ssl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;body&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;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()[:&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;server&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Server&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="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;extract_title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;interesting&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;THRESHOLD&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;The &lt;code&gt;score()&lt;/code&gt; function flags the things humans actually care about: debug endpoints, admin panels, verbose error pages, default credentials banners, and misconfigured CORS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 4 — Capture and brief.&lt;/strong&gt; Headless screenshots for anything new, then a markdown brief per program: what's new since last run, what looks interesting, and direct links.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Deduplication — the part everyone skips
&lt;/h2&gt;

&lt;p&gt;The difference between a pipeline you love and one you abandon in a week is dedup. The kit keeps a hash store per program (&lt;code&gt;state/&amp;lt;program&amp;gt;.seen.json&lt;/code&gt;). Each run, only &lt;em&gt;new&lt;/em&gt; assets, &lt;em&gt;changed&lt;/em&gt; response hashes, and &lt;em&gt;new&lt;/em&gt; ports make it into your brief. A quiet program produces a two-line report instead of 4,000 lines of déjà vu.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Schedule it and walk away
&lt;/h2&gt;

&lt;p&gt;On the Pi (or any always-on box), a single cron entry does the work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# every night at 02:00 — full pipeline, all configured programs
0 2 * * * cd /home/sean/bounty-kit &amp;amp;&amp;amp; .venv/bin/python run_pipeline.py &amp;gt;&amp;gt; state/cron.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The orchestrator checkpoints after every stage, so a crash at stage 3 resumes at stage 3 instead of starting over. That boring reliability is most of what you're paying for in any automation you intend to keep.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Alerts that don't cry wolf
&lt;/h2&gt;

&lt;p&gt;The final piece is a digest hook — mine posts to Telegram. The rule I tuned for: only notify on &lt;em&gt;new + interesting&lt;/em&gt; (score above threshold AND not in the seen-store). Everything else waits for the morning brief. My notification rate dropped from dozens per run to one or two per week, and every one was worth reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you actually get for $15
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://uln.lemonsqueezy.com/checkout/buy/763b023d-bfb5-475d-ab28-9ba0e9ba142d" rel="noopener noreferrer"&gt;kit&lt;/a&gt; includes all four stage scripts, the orchestrator with checkpointing, scope templates for the major platforms, the dedup store, and the Telegram/Slack digest hooks — plus a setup walkthrough. If you want the agent layer on top (LLM triage that reads results and drafts your report skeleton), the &lt;a href="https://uln.lemonsqueezy.com/checkout/buy/0ce2371c-c75d-423c-b64d-685a00445048" rel="noopener noreferrer"&gt;$9 AI Agent Toolkit&lt;/a&gt; plugs into the same state directory, and everything lives in the &lt;a href="https://github.com/ulnit/agent-store" rel="noopener noreferrer"&gt;agent store&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Recon will never write a vulnerability report for you — that part still requires eyes and judgment. But it can absolutely stop eating your evenings. Set the scope, schedule the run, and let the pipeline be bored so you don't have to be.&lt;/p&gt;

</description>
      <category>python</category>
      <category>automation</category>
      <category>tutorial</category>
      <category>ai</category>
    </item>
    <item>
      <title>7 Raspberry Pi Hacks That Automate My Entire Digital Life (24/7, Under 5 Watts)</title>
      <dc:creator>ULNIT</dc:creator>
      <pubDate>Fri, 21 Aug 2026 01:02:45 +0000</pubDate>
      <link>https://dev.to/ulnit/7-raspberry-pi-hacks-that-automate-my-entire-digital-life-247-under-5-watts-4006</link>
      <guid>https://dev.to/ulnit/7-raspberry-pi-hacks-that-automate-my-entire-digital-life-247-under-5-watts-4006</guid>
      <description>&lt;p&gt;A Raspberry Pi is not a toy. It's a $35, 4-watt Linux machine with root access, a GPIO header, and network connectivity — everything you need to build automation that would cost $20/month on a VPS. I've been running a Pi 4 as my personal automation server for over a year now, and it has quietly become the most useful computer I own.&lt;/p&gt;

&lt;p&gt;Here are seven setups — call them hacks, projects, or workflows — that made the biggest difference. All of them run headless, all of them survive reboots, and none of them required more than an afternoon to build.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Network Watchdog That Reboots My Router
&lt;/h2&gt;

&lt;p&gt;My ISP's router hangs roughly once a week. Instead of noticing the outage and fixing it by hand, the Pi notices for me. A cron job pings a couple of reliable hosts every five minutes; if both fail three times in a row, it toggles a smart plug via a local API and waits for the router to come back.&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;# watchdog.py — runs every 5 min via cron
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;internet_ok&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.1.1.1&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;9.9.9.9&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ping&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;-c1&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;-W2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                           &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CalledProcessError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="n"&gt;fails&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/tmp/watchdog_fails&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="c1"&gt;# ... persist fail count, trip smart plug at 3 consecutive failures
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since deploying this, my household has had exactly zero "internet is down and nobody knows" moments.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. An Ad-Hoc DNS Sinkhole for the Whole LAN
&lt;/h2&gt;

&lt;p&gt;Install Pi-hole on the Pi, point your router's DHCP DNS at it, and every device on the network gets ad blocking for free. That's the standard setup. The hack part: I feed it a cron job that merges community blocklists with my own — any domain that appears in my web server access logs more than 50 times a day from bots gets auto-blacklisted. The Pi defends its own attention budget.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The "Digital Tripwire" With a Webcam
&lt;/h2&gt;

&lt;p&gt;An old USB webcam + OpenCV gives you a motion-detecting security camera in about 30 lines. The trick that makes it actually useful: instead of streaming video, the Pi only wakes fully when motion crosses a threshold, captures a burst of frames, and pushes them to me via a Telegram bot. Idle power stays low, and I get a photo instead of a firehose.&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;
&lt;span class="n"&gt;cap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;VideoCapture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;
    &lt;span class="n"&gt;gray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cvtColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;COLOR_BGR2GRAY&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;absdiff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;send_snapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# Telegram bot API call
&lt;/span&gt;    &lt;span class="n"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gray&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Overnight AI Recon on a Schedule
&lt;/h2&gt;

&lt;p&gt;This is where the Pi stops doing chores and starts doing knowledge work. Every night at 2am, a cron entry kicks off an agent loop that checks my inbox, reads RSS feeds, scans security advisories for software I actually run, and writes a morning briefing as markdown. I read it with coffee; the Pi did the reading at 2am.&lt;/p&gt;

&lt;p&gt;The agent plumbing — LLM client with retries, checkpointing so a crash resumes instead of restarting, structured JSON outputs — is the exact stack that ships in my &lt;a href="https://uln.lemonsqueezy.com/checkout/buy/0ce2371c-c75d-423c-b64d-685a00445048" rel="noopener noreferrer"&gt;AI Agent Toolkit&lt;/a&gt; ($9, one-time). You can absolutely hand-roll all of it, but if you want the boring parts pre-built so you can focus on what the agent actually does, that's what it's for. It runs happily on a Pi 4 with 2GB of RAM because the heavy lifting happens in the cloud — the Pi just orchestrates.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Automated Backup Sentry
&lt;/h2&gt;

&lt;p&gt;The Pi runs my restic backups to two destinations: a USB drive and an offsite B2 bucket. The hack isn't the backup script — it's the &lt;em&gt;verification&lt;/em&gt; job. Weekly, it restores a random file from last week's snapshot and checks the hash. Backups you've never restored are just optimistic fiction; this turns mine into tested fact. Failures (and successes) land in the morning briefing from hack #4.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Bug Bounty Recon on a Timer
&lt;/h2&gt;

&lt;p&gt;Security researchers know the drill: subdomain enumeration, port scanning, screenshot capture. Doing it manually is a waste of a human. My Pi runs the same four-stage pipeline every night against targets I'm authorized to test — asset discovery, passive fingerprinting, change detection against yesterday's results, and a diff report. New subdomains show up in my inbox before breakfast.&lt;/p&gt;

&lt;p&gt;The full pipeline, with every script and config, is documented step by step in my &lt;a href="https://uln.lemonsqueezy.com/checkout/buy/763b023d-bfb5-475d-ab28-9ba0e9ba142d" rel="noopener noreferrer"&gt;Bug Bounty Automation Kit&lt;/a&gt; — a $15 tutorial + toolkit. The Pi's low power draw matters here: recon is a waiting game, and a machine that costs pennies per month can afford to be patient.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. The Pi That Monitors Itself
&lt;/h2&gt;

&lt;p&gt;Meta, but essential: a tiny daemon logs CPU temperature, memory, disk usage, and every cron job's exit status to SQLite. A nightly query flags anything anomalous. My Pi has warned me about a filling SD card and a runaway script twice now — both times before anything broke. If you run any of the above, build this first.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bigger Picture
&lt;/h2&gt;

&lt;p&gt;Every one of these follows the same pattern: cheap hardware + cron + a little Python beats paying for a service or doing it by hand. The Pi isn't powerful, and that's the feature — it forces lean, focused automation, and it never sends you a bill.&lt;/p&gt;

&lt;p&gt;All of the source for these setups lives in my &lt;a href="https://github.com/ulnit/agent-store" rel="noopener noreferrer"&gt;agent store repo&lt;/a&gt;, and the two kits linked above are the deepest dives if you want to go further.&lt;/p&gt;

&lt;p&gt;Which one are you building first? I'd start with #1 or #5 — both take under two hours and pay off immediately.&lt;/p&gt;

</description>
      <category>raspberrypi</category>
      <category>automation</category>
      <category>python</category>
      <category>ai</category>
    </item>
    <item>
      <title>10 Boring AI Automation Tricks That Keep My Agents Running for Months</title>
      <dc:creator>ULNIT</dc:creator>
      <pubDate>Thu, 20 Aug 2026 01:03:13 +0000</pubDate>
      <link>https://dev.to/ulnit/10-boring-ai-automation-tricks-that-keep-my-agents-running-for-months-4ipi</link>
      <guid>https://dev.to/ulnit/10-boring-ai-automation-tricks-that-keep-my-agents-running-for-months-4ipi</guid>
      <description>&lt;p&gt;I've spent the last year building small AI automations that actually run unattended — on a Raspberry Pi, a cheap VPS, and my laptop. Most AI automation content shows you the fun part (a prompt, a demo, a wow moment). Almost none of it shows you the part that decides whether the thing survives contact with reality.&lt;/p&gt;

&lt;p&gt;Here are the tips and tricks that made the difference between scripts that die in a week and automations I haven't touched in months. Every one of these is boring. That's the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Never let the model be the only thing holding state
&lt;/h2&gt;

&lt;p&gt;The #1 reason agent scripts fail: they keep state in memory or in the conversation, and when the process dies, everything is gone. Write every step to disk — SQLite is perfect for this. If your automation can answer "what was I doing, and what's left?", it can survive restarts, crashes, and reboots. On my Pi, every agent writes a checkpoint before and after each tool call. If it dies mid-run, the next invocation resumes instead of starting over.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Wrap every LLM call like it's an unreliable API
&lt;/h2&gt;

&lt;p&gt;Because it is. Your wrapper needs: retries with exponential backoff, a timeout, token/usage accounting, and a fallback. I keep a tiny &lt;code&gt;LLMClient&lt;/code&gt; class that does all four. Ten lines of retry logic has saved me more times than any clever prompt. If you're rolling agents by hand, this is also exactly the kind of plumbing I packaged up in the &lt;a href="https://uln.lemonsqueezy.com/checkout/buy/0ce2371c-c75d-423c-b64d-685a00445048" rel="noopener noreferrer"&gt;AI Agent Toolkit&lt;/a&gt; — the client, the agent loop, logging, and config as clean Python modules, $9 one-time. But you can (and should) build your own first; you'll understand it better.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Structure your outputs, don't parse prose
&lt;/h2&gt;

&lt;p&gt;Never ask the model to "write a report" and then regex the result. Ask for JSON with an explicit schema, validate it, and retry on failure. I use a small validation loop: request JSON, attempt to parse, if it fails feed the error back to the model once. Two tries is almost always enough. Structured output turns a flaky text generator into a reliable data source.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Give tools tight contracts and narrow permissions
&lt;/h2&gt;

&lt;p&gt;An agent that can "run any shell command" is a liability, not a feature. Give it specific tools: &lt;code&gt;list_subdomains&lt;/code&gt;, &lt;code&gt;fetch_url&lt;/code&gt;, &lt;code&gt;write_report&lt;/code&gt;. Each tool has a defined input and output. Narrow tools are easier to test, easier to log, and much safer. This is the core idea behind the recon pipeline in my &lt;a href="https://uln.lemonsqueezy.com/checkout/buy/763b023d-bfb5-475d-ab28-9ba0e9ba142d" rel="noopener noreferrer"&gt;Bug Bounty Automation Kit&lt;/a&gt; — enumeration, scanning, and triage as discrete, auditable steps rather than one black-box "agent."&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Log like you'll be debugging at 3 AM
&lt;/h2&gt;

&lt;p&gt;You will be. Log the prompt, the raw response, the tool calls, and the timing — but log them to structured files (JSONL), not just stdout. Then a quick &lt;code&gt;jq&lt;/code&gt; query tells you exactly where things went sideways. Timestamp everything. On a headless Pi with no monitor, your logs are your eyes.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Schedule with cron, not cleverness
&lt;/h2&gt;

&lt;p&gt;People reach for Kubernetes and message queues for things that cron handles perfectly. A single crontab line plus a systemd timer for retries covers 95% of "run this every night" needs. Keep the scheduler dumb and the script robust. Complexity is a cost; spend it only where it earns its keep.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Make the last step a human-readable report
&lt;/h2&gt;

&lt;p&gt;The best trick I learned: end every automation with a step that produces a short, readable summary — a markdown file or an email. Not a dashboard. Not a metric. One paragraph of "here's what I did and what I found." It turns a background process into something you actually check, which is how you notice problems early. My recon runs end with a markdown report that's genuinely pleasant to read.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Run it on weak hardware on purpose
&lt;/h2&gt;

&lt;p&gt;If your automation runs on a Raspberry Pi with 2 GB of RAM, it runs anywhere. I test everything on the Pi first. It forces small memory footprints, honest about resource use, and it catches assumptions about speed and availability that would hide on a beefy machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Version your prompts and configs like code
&lt;/h2&gt;

&lt;p&gt;Put prompts, model names, and thresholds in a config file, and commit it to git. When output quality changes, you can bisect. "It worked last Tuesday" is a solvable problem when your prompts are versioned; it's a mystery when they're inline strings.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Ship boring, fix clever
&lt;/h2&gt;

&lt;p&gt;Get the dumb-but-reliable version running end to end before you optimize. A pipeline that produces mediocre results every single night is infinitely more valuable than a brilliant one that runs when it feels like it. You can improve quality later; you can't improve a thing that isn't running.&lt;/p&gt;




&lt;p&gt;That's the whole playbook. None of it is exciting, and all of it compounds: state on disk, retries, structured output, tight tools, honest logging, dumb scheduling, readable reports, weak-hardware testing, versioned configs, and boring-first shipping.&lt;/p&gt;

&lt;p&gt;Stack those ten habits and your AI automations stop being demos and start being infrastructure. Happy building.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>python</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I Spent a Week Testing the $9 AI Agent Toolkit — Here's What's Actually Inside</title>
      <dc:creator>ULNIT</dc:creator>
      <pubDate>Wed, 19 Aug 2026 01:02:42 +0000</pubDate>
      <link>https://dev.to/ulnit/i-spent-a-week-testing-the-9-ai-agent-toolkit-heres-whats-actually-inside-4cbd</link>
      <guid>https://dev.to/ulnit/i-spent-a-week-testing-the-9-ai-agent-toolkit-heres-whats-actually-inside-4cbd</guid>
      <description>&lt;p&gt;A week ago I picked up the &lt;a href="https://uln.lemonsqueezy.com/checkout/buy/0ce2371c-c75d-423c-b64d-685a00445048" rel="noopener noreferrer"&gt;AI Agent Toolkit&lt;/a&gt; — a $9 bundle of pre-built automation agents — mostly out of curiosity. Nine dollars is coffee money, so my bar was low: if one component saved me an afternoon, it paid for itself. After a week of actually running it on a Raspberry Pi 5 and a spare laptop, here's an honest breakdown of what's inside, what works, and what you should know before you buy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Toolkit Actually Is
&lt;/h2&gt;

&lt;p&gt;The toolkit is a collection of Python-based automation agents and the scaffolding to run them: task runners, prompt templates, state management, and a small framework for chaining agents together. It's not a hosted SaaS — everything runs on your own hardware, which I like. My Pi 5 (8 GB) handled the full stack with plenty of headroom.&lt;/p&gt;

&lt;p&gt;The core pieces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agent runner&lt;/strong&gt; — a lightweight loop that takes a task definition, calls an LLM, executes tool calls, and tracks state between runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt + workflow templates&lt;/strong&gt; — pre-written scaffolds for common jobs: summarization, triage, data extraction, report generation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration glue&lt;/strong&gt; — connectors for email, RSS, webhooks, and Telegram, which is where most of the practical value lives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State store&lt;/strong&gt; — a simple SQLite-backed dedup layer so agents never process the same item twice.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I Built With It in a Week
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Day 1–2: Inbox triage.&lt;/strong&gt; I pointed the email agent at a secondary inbox that gets ~120 messages a day. It classifies into "act today", "FYI", and "noise", then sends me a Telegram digest at 7 AM. Accuracy was honestly better than I expected — around 90% on the first day, and better after I tweaked the classification prompt. The template got me 80% of the way there; the last 20% was prompt tuning, which is exactly how it should be.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 3–4: RSS synthesis.&lt;/strong&gt; ~35 feeds, deduplicated, summarized overnight. The state store is the unsung hero here — without it, every agent framework I've tried eventually re-summarizes the same story from three outlets. This one didn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 5–7: chaining.&lt;/strong&gt; The interesting part. I wired the triage output into the report agent, so anything marked "act today" automatically lands in a daily action list. Chaining agents is just YAML-style task definitions pointing at each other — no message broker, no Kubernetes, nothing. It's deliberately small, which is its biggest strength.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Good, The Meh, and The Honest Caveats
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The good:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's real, runnable code — not a Notion doc of prompts. You can read it, fork it, and extend it.&lt;/li&gt;
&lt;li&gt;Runs entirely self-hosted. Your API keys, your hardware, your data.&lt;/li&gt;
&lt;li&gt;The boring parts (state, dedup, retries, scheduling) are already solved, and those are the parts nobody wants to write.&lt;/li&gt;
&lt;li&gt;At $9, the risk-reward ratio is almost silly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The meh:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Documentation is functional but thin in spots. I had to read the source to understand a couple of config options — fine for me, potentially annoying if you're newer to Python.&lt;/li&gt;
&lt;li&gt;It assumes you're comfortable with the command line. There's no GUI, and that's a deliberate choice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The caveats:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You still bring your own LLM API key, so there's an ongoing cost (mine runs about $0.10/day for the whole pipeline).&lt;/li&gt;
&lt;li&gt;It won't magically build novel automations for you — it's a toolkit, not a genie. The value is skipping the plumbing, not skipping the thinking.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Who Should (and Shouldn't) Buy It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Buy it if&lt;/strong&gt; you want working agent scaffolding you can deploy on your own hardware this weekend, you're comfortable with Python basics, and you'd rather adapt real code than write a framework from scratch. For $9, even one working pipeline is a win.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skip it if&lt;/strong&gt; you want a no-code, point-and-click product — this is a builder's toolkit, and the audience is people who like reading the source anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Verdict
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://uln.lemonsqueezy.com/checkout/buy/0ce2371c-c75d-423c-b64d-685a00445048" rel="noopener noreferrer"&gt;AI Agent Toolkit&lt;/a&gt; earned its keep in my setup by day three. It's not magic — it's well-organized, self-hosted automation code that removes the 80% of agent-building that's pure boilerplate. If you've been meaning to run your own agents but keep getting stuck on the scaffolding, this is a cheap way to unstick yourself.&lt;/p&gt;

&lt;p&gt;Full disclosure: I'm a fan of the project and may earn a commission on the link above. Everything in this review reflects a week of actual use on my own hardware.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you build something with it, I'd genuinely like to hear what — drop a comment.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How I Built an AI Agent That Reads the News and My Inbox Before I Wake Up</title>
      <dc:creator>ULNIT</dc:creator>
      <pubDate>Tue, 18 Aug 2026 01:02:22 +0000</pubDate>
      <link>https://dev.to/ulnit/how-i-built-an-ai-agent-that-reads-the-news-and-my-inbox-before-i-wake-up-2e22</link>
      <guid>https://dev.to/ulnit/how-i-built-an-ai-agent-that-reads-the-news-and-my-inbox-before-i-wake-up-2e22</guid>
      <description>&lt;h1&gt;
  
  
  How I Built an AI Agent That Reads the News and My Inbox Before I Wake Up
&lt;/h1&gt;

&lt;p&gt;For years my morning routine was the same: reach for the phone, open email, open a few news sites, and lose 40 minutes to a mix of newsletters I didn't care about and headlines designed to spike my cortisol. By the time I actually sat down to work, the best part of my focus was gone — spent on information that, honestly, an intern could have filtered for me.&lt;/p&gt;

&lt;p&gt;So I built the intern. An AI agent, running on a Raspberry Pi sitting on my desk, that reads everything for me overnight and hands me a one-page digest with my coffee. This is the story of how I built it, what broke, and what I'd do differently.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Goal
&lt;/h2&gt;

&lt;p&gt;I wanted three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Inbox triage&lt;/strong&gt; — sort email into "act today", "FYI", and "noise" before I see it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;News synthesis&lt;/strong&gt; — follow ~40 RSS sources and summarize only what's genuinely new, deduplicated across outlets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One artifact&lt;/strong&gt; — a single markdown digest delivered to Telegram at 6:30 AM. No apps to open, no feeds to scroll.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The hard part isn't any of these individually. It's doing them reliably, cheaply, and without the agent hallucinating a news story that doesn't exist.&lt;/p&gt;

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

&lt;p&gt;The pipeline is deliberately boring:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[IMAP + RSS collectors] → [dedup/state store] → [LLM triage agent] → [digest renderer] → Telegram
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything runs on cron. The collectors are dumb scripts; the LLM only ever sees &lt;em&gt;new&lt;/em&gt; items since yesterday's run. That single decision — never re-send the full dataset to the model — keeps the whole thing under ~$0.10/day in API costs.&lt;/p&gt;

&lt;p&gt;The collectors are straightforward Python:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;imaplib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;feedparser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_unseen_emails&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;since_hours&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;mail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;imaplib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IMAP4_SSL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;imap.gmail.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EMAIL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;APP_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INBOX&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UNSEEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(RFC822)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message_from_bytes&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&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;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;From&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;subject&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Subject&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;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_payload&lt;/span&gt;&lt;span class="p"&gt;())[:&lt;/span&gt;&lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;],&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;items&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_feeds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;feedparser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&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;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;summary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;summary&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="p"&gt;)[:&lt;/span&gt;&lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;link&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;,&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;items&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A state file tracks which item IDs have already been processed, so the agent only triages fresh material.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Agent Layer
&lt;/h2&gt;

&lt;p&gt;This is where most DIY versions fall apart. My first attempt handed the LLM a wall of text and said "summarize this." The result was bland, generic, and occasionally invented connections between stories that didn't exist.&lt;/p&gt;

&lt;p&gt;What actually works is constraining the agent hard:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Structured output only.&lt;/strong&gt; Each item must be classified (&lt;code&gt;act_today&lt;/code&gt; / &lt;code&gt;fyi&lt;/code&gt; / &lt;code&gt;noise&lt;/code&gt; for email, &lt;code&gt;read&lt;/code&gt; / &lt;code&gt;skip&lt;/code&gt; for news) with a one-line justification.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No synthesis across items.&lt;/strong&gt; The agent summarizes each item independently; &lt;em&gt;I&lt;/em&gt; do the connecting. This killed hallucinations almost entirely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A scoring rubric in the prompt.&lt;/strong&gt; "Relevant to: self-hosting, security research, LLM tooling, Raspberry Pi. Everything else is noise unless it's genuinely major."
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;You are a triage agent. For each item below, return JSON:
{verdict: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;act_today&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fyi&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;noise&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;read&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;skip&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, reason: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;10 words&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;}
Relevance rubric: self-hosting, security research, LLM tooling,
Raspberry Pi. Be ruthless — default to noise/skip.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The digest renderer then groups verdicts into sections and sends the markdown to Telegram via the bot API. Total runtime: about 3 minutes per morning.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Broke (and What I'd Do Differently)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Rate limits.&lt;/strong&gt; My first version fired one API call per item. Batching items into groups of 10 cut cost and latency by 80% with no quality loss.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Feed noise.&lt;/strong&gt; Some RSS feeds duplicate stories across categories. Deduplicating by title similarity (a quick difflib ratio check) before the LLM stage saved a third of the tokens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Plumbing vs. thinking.&lt;/strong&gt; Here's my honest takeaway: the interesting 20% of this project was the prompt engineering and the triage rubric. The other 80% — collectors, state management, retries, scheduling, output formatting — is solved plumbing you should not hand-roll. I ended up rebuilding the scaffolding on top of the &lt;a href="https://uln.lemonsqueezy.com/checkout/buy/0ce2371c-c75d-423c-b64d-685a00445048" rel="noopener noreferrer"&gt;AI Agent Toolkit&lt;/a&gt;, a $9 set of ready-made agent patterns and scripts that gave me the collector/state/delivery boilerplate in an afternoon. If you're building something like this, starting there is the difference between a weekend project and a month of yak-shaving. The same skeleton, incidentally, powers my security monitoring too — the &lt;a href="https://uln.lemonsqueezy.com/checkout/buy/763b023d-bfb5-475d-ab28-9ba0e9ba142d" rel="noopener noreferrer"&gt;Bug Bounty Automation Kit&lt;/a&gt; is the same idea pointed at recon pipelines instead of inboxes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;My screen time in the first hour of the day dropped from ~40 minutes to ~5. The digest is one Telegram message: five emails that actually need answers, six stories worth reading, nothing else. Some days the "noise" bucket has 60 items and I feel zero guilt ignoring it.&lt;/p&gt;

&lt;p&gt;The bigger lesson: agents aren't magic, they're &lt;em&gt;interns&lt;/em&gt;. They're brilliant when you give them a rubric, a narrow scope, and a format to fill in — and useless when you hand them ambiguity. Design the constraints first; the intelligence takes care of itself.&lt;/p&gt;

&lt;p&gt;If you build one of these, start small: ten feeds, one inbox, one delivery channel. You can always widen the aperture once the intern stops making things up.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>raspberrypi</category>
      <category>python</category>
    </item>
    <item>
      <title>I Let an AI Agent Run My Bug Bounty Recon Overnight — Here's the Setup That Actually Works</title>
      <dc:creator>ULNIT</dc:creator>
      <pubDate>Mon, 17 Aug 2026 01:02:48 +0000</pubDate>
      <link>https://dev.to/ulnit/i-let-an-ai-agent-run-my-bug-bounty-recon-overnight-heres-the-setup-that-actually-works-2lm4</link>
      <guid>https://dev.to/ulnit/i-let-an-ai-agent-run-my-bug-bounty-recon-overnight-heres-the-setup-that-actually-works-2lm4</guid>
      <description>&lt;h1&gt;
  
  
  I Let an AI Agent Run My Bug Bounty Recon Overnight — Here's the Setup That Actually Works
&lt;/h1&gt;

&lt;p&gt;Last month I was spending my evenings doing the most boring part of bug bounty hunting by hand: running subfinder, httpx, and nuclei against targets, copy-pasting results between terminal windows, and babysitting a laptop that needed to stay awake. The actual interesting part — reading responses, spotting weird endpoints, testing hypotheses — kept getting pushed to "tomorrow."&lt;/p&gt;

&lt;p&gt;So I automated the boring part. All of it. The agent I ended up with runs on a Raspberry Pi, costs less than a coffee to operate, and hands me a triaged report every morning. Here's how it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Idea
&lt;/h2&gt;

&lt;p&gt;Most automation tutorials stop at "here's a bash script with a cron job." That's fine for fixed pipelines, but bug bounty targets move: new subdomains appear, services change ports, an endpoint that returned 404 yesterday starts returning 200 today. A static script can't decide that a newly-exposed debug panel on a staging subdomain is worth escalating and a CDN edge is not. An agent can.&lt;/p&gt;

&lt;p&gt;The architecture is three layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Collectors&lt;/strong&gt; — dumb, reliable scripts that gather raw data (subdomains, live hosts, ports, response hashes).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diff engine&lt;/strong&gt; — compares today's snapshot against yesterday's. Everything unchanged gets discarded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent layer&lt;/strong&gt; — an LLM agent that only ever sees the &lt;em&gt;diff&lt;/em&gt;, classifies each change, and decides what deserves deeper probing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The agent never touches the full dataset. That's the trick that keeps API costs sane and keeps the agent focused.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pipeline in Practice
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# collectors run at 02:00, diff at 04:00, agent at 05:00&lt;/span&gt;
0 2 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /opt/recon/collect.sh &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /var/log/recon/collect.log 2&amp;gt;&amp;amp;1
0 4 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; python3 /opt/recon/diff.py &lt;span class="nt"&gt;--state&lt;/span&gt; /data/state.json
0 5 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; python3 /opt/recon/agent_triage.py &lt;span class="nt"&gt;--input&lt;/span&gt; /data/todays_diff.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The collectors are the usual suspects:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;collect_subdomains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subfinder&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;-d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-silent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                         &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splitlines&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;probe_live&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subdomains&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;httpx&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;-silent&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;-status-code&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;-title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                         &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subdomains&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                         &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&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;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splitlines&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The diff engine writes out JSON with three buckets: &lt;code&gt;new_assets&lt;/code&gt;, &lt;code&gt;changed_responses&lt;/code&gt;, and &lt;code&gt;gone_assets&lt;/code&gt;. Gone assets are underrated — a subdomain suddenly disappearing often means it's about to become dangling and takeable over.&lt;/p&gt;

&lt;p&gt;The agent then gets a prompt roughly like: "Here are 14 changes since yesterday. For each, output a JSON verdict: IGNORE, WATCH, or INVESTIGATE, plus a one-line reason. You have access to a tool that fetches a URL and returns headers + first 500 bytes."&lt;/p&gt;

&lt;p&gt;It's shocking how well this works. It correctly flagged a &lt;code&gt;/graphql&lt;/code&gt; endpoint that appeared on a marketing subdomain as INVESTIGATE and waved through forty "server header changed" noise items as IGNORE. That triage pass used to take me an hour of scrolling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a Raspberry Pi?
&lt;/h2&gt;

&lt;p&gt;This whole stack idles at a few hundred megabytes of RAM. A Pi 4 or 5 runs the collectors, the state database, and the agent orchestrator without breaking a sweat, draws a few watts, and never needs to be "on" as a separate decision — it's just always there. I've got mine headless on my desk doing recon every night while I sleep. The only thing that isn't local is the LLM call for triage, and because it only sees the diff, a typical night costs pennies in tokens.&lt;/p&gt;

&lt;p&gt;The one operational lesson: keep your state in a real database (I use SQLite), not flat files. The night a collector crashed mid-write, flat-file state silently corrupted and the diff engine reported 2,000 "new" subdomains. SQLite transactions made that class of bug impossible.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Part That Took Me Too Long to Figure Out
&lt;/h2&gt;

&lt;p&gt;Rate-limit yourself &lt;em&gt;before&lt;/em&gt; the target does. Run httpx with &lt;code&gt;-rl 10&lt;/code&gt;, spread collectors across the night, and never parallelize nuclei templates that send writes. The goal is to look like the most polite scanner that ever existed. Bonus: slower, spaced-out requests catch flaky endpoints that fast bursts miss.&lt;/p&gt;

&lt;h2&gt;
  
  
  If You Don't Want to Build It From Scratch
&lt;/h2&gt;

&lt;p&gt;Honestly, building this from scratch was a weekend of plumbing, not hacking. The interesting work was designing the agent's verdict schema and testing it against real diffs. If you'd rather skip the plumbing, the &lt;a href="https://uln.lemonsqueezy.com/checkout/buy/763b023d-bfb5-475d-ab28-9ba0e9ba142d" rel="noopener noreferrer"&gt;Bug Bounty Automation Kit&lt;/a&gt; packages up exactly this pattern — collectors, diffing, and agent triage wired together for $15, which is less than one hour of my time used to cost me. It's what I started from before customizing it, and it drops straight onto a Pi. If your targets are more general automation than bug bounty, the &lt;a href="https://uln.lemonsqueezy.com/checkout/buy/0ce2371c-c75d-423c-b64d-685a00445048" rel="noopener noreferrer"&gt;AI Agent Toolkit&lt;/a&gt; ($9) covers the same agent-loop fundamentals for broader use cases. More resources in the &lt;a href="https://github.com/ulnit/agent-store" rel="noopener noreferrer"&gt;agent store&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start Tonight
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Get subfinder, httpx, and nuclei running against one target, manually.&lt;/li&gt;
&lt;li&gt;Add the diff layer — even a JSON file in git works at first.&lt;/li&gt;
&lt;li&gt;Put an LLM call in front of the diff and iterate on the verdict prompt until it stops hallucinating interest in CDN noise.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then sleep in. The morning report will be waiting.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What's the most boring part of your recon that you haven't automated yet? That's your next project.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>5 Raspberry Pi Automation Builds That Pay for Themselves</title>
      <dc:creator>ULNIT</dc:creator>
      <pubDate>Sun, 16 Aug 2026 01:02:57 +0000</pubDate>
      <link>https://dev.to/ulnit/5-raspberry-pi-automation-builds-that-pay-for-themselves-16ag</link>
      <guid>https://dev.to/ulnit/5-raspberry-pi-automation-builds-that-pay-for-themselves-16ag</guid>
      <description>&lt;p&gt;A Raspberry Pi is the most underrated automation hardware you can buy. It costs about the same as a pizza, sips 2–5 watts, runs a full Linux stack, and exposes GPIO pins for when you want to touch the physical world. I've been running Raspberry Pis as always-on automation boxes for years — scraping, monitoring, alerting, and orchestrating AI agents — and these are the five builds that have proven most worth the effort, plus what I've learned keeping them alive 24/7.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a Pi beats a cloud VM for personal automation
&lt;/h2&gt;

&lt;p&gt;Most personal automation is small: a handful of cron jobs, a few HTTP requests a minute, a few megabytes of logs a day. For workloads like that, a Pi wins on three fronts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost&lt;/strong&gt;: a one-time $35–75 instead of $5–10/month forever. It pays for itself inside a year.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy&lt;/strong&gt;: your scraping history, credentials, and data never leave your network.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control&lt;/strong&gt;: root access, systemd, GPIO, no hypervisor restrictions. If you can do it on Linux, you can do it on a Pi.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The catch is reliability. SD cards die, cheap power supplies sag, and memory leaks compound. Every build below assumes the boring basics are handled: a quality power supply, a high-endurance microSD card (or better, USB SSD boot), and services managed by &lt;code&gt;systemd&lt;/code&gt; instead of scripts left running in a tmux session.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build #1: The web watchdog
&lt;/h2&gt;

&lt;p&gt;The gateway drug of Pi automation: a Python script that polls a list of URLs, hashes the responses, and pushes a notification when anything changes. Price trackers, release monitors, "is this expired domain still available" checkers — same loop every time. The trick is to diff &lt;em&gt;meaningfully&lt;/em&gt;: hash extracted text content, not raw HTML, or you'll get 3 a.m. alerts because a cache-buster parameter changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build #2: DNS-level automation for your whole network
&lt;/h2&gt;

&lt;p&gt;Run a DNS sinkhole on the Pi and suddenly your automation covers every device in the house. Log every query, block ad and tracker domains, and wire up alerts for anomalies — like an IoT device phoning home to a country you've never heard of. That single alert has caught misbehaving smart devices for me more than once. The Pi's low power draw makes it a perfect always-on DNS server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build #3: AI agents on a schedule
&lt;/h2&gt;

&lt;p&gt;A Pi 5 has plenty of muscle to orchestrate LLM-powered agents. It won't run big local models well, but it's excellent as the orchestration layer: a cron job fires, the script calls an LLM API, the agent does research, writing, summarizing, or triage, and the result lands in your inbox, a file, or a webhook. One of mine runs every morning, digests my overnight alerts, and messages me a summary. The plumbing for agent jobs is mostly boilerplate — which is why I packaged mine up. If you want a starting point, my &lt;a href="https://uln.lemonsqueezy.com/checkout/buy/0ce2371c-c75d-423c-b64d-685a00445048" rel="noopener noreferrer"&gt;AI Agent Toolkit ($9)&lt;/a&gt; includes the scheduling patterns, prompt templates, and glue code I use on my own Pi fleet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build #4: The bug bounty recon box
&lt;/h2&gt;

&lt;p&gt;Recon is 90% of bug bounty hunting, and it's almost entirely automatable: subdomain enumeration, port scans, screenshotting, technology fingerprinting. A Pi makes a great dedicated recon box — it's slow, but it runs forever, and its natural rate-limiting actually keeps you polite to your targets. Point it at your authorized scope overnight and wake up to a fresh report. I wrote up my complete pipeline in my &lt;a href="https://uln.lemonsqueezy.com/checkout/buy/763b023d-bfb5-475d-ab28-9ba0e9ba142d" rel="noopener noreferrer"&gt;Bug Bounty Automation Kit ($15)&lt;/a&gt;, including the exact toolchain and how to keep everything legal and in-scope.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build #5: Touch the physical world
&lt;/h2&gt;

&lt;p&gt;GPIO is where the Pi stops being a tiny server and becomes a &lt;em&gt;hack&lt;/em&gt;. Relay boards and ten dollars get you control of lights, fans, or a door strike. A $3 motion sensor becomes a security tripwire that snaps a camera frame and pushes it to your phone. My favorite: a reed switch on the mailbox that logs every delivery. None of it requires more than a few lines of Python and the &lt;code&gt;gpiozero&lt;/code&gt; library.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping a Pi alive for years
&lt;/h2&gt;

&lt;p&gt;Three lessons from fleet members with 400+ day uptimes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;systemd + auto-restart.&lt;/strong&gt; &lt;code&gt;Restart=always&lt;/code&gt; plus a watchdog timer means crashes self-heal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log to RAM.&lt;/strong&gt; Point chatty logs at tmpfs or silence them, and your SD card lives years longer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor the monitor.&lt;/strong&gt; Dead automation is silent. Have a second system — even another Pi — ping your services and scream when they stop answering.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The bottom line
&lt;/h2&gt;

&lt;p&gt;A $35 board, a few Python scripts, and an afternoon of setup buys you infrastructure that would cost $20+ a month in the cloud — and it all stays on your terms. Start with one watchdog script and let the ideas compound from there.&lt;/p&gt;

</description>
      <category>raspberrypi</category>
      <category>automation</category>
      <category>python</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
