<?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: Domonique Luchin</title>
    <description>The latest articles on DEV Community by Domonique Luchin (@domoniqueluchin).</description>
    <link>https://dev.to/domoniqueluchin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3816446%2F43258dea-c4c5-4bc1-95b1-9e05955201f1.png</url>
      <title>DEV Community: Domonique Luchin</title>
      <link>https://dev.to/domoniqueluchin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/domoniqueluchin"/>
    <language>en</language>
    <item>
      <title>Reading Structural Drawings as an Engineer: The Coordination Review Workflow</title>
      <dc:creator>Domonique Luchin</dc:creator>
      <pubDate>Mon, 25 May 2026 10:00:04 +0000</pubDate>
      <link>https://dev.to/domoniqueluchin/reading-structural-drawings-as-an-engineer-the-coordination-review-workflow-en5</link>
      <guid>https://dev.to/domoniqueluchin/reading-structural-drawings-as-an-engineer-the-coordination-review-workflow-en5</guid>
      <description>&lt;p&gt;I've reviewed hundreds of structural drawing sets over my 6 years in oil and gas. Equipment platforms, pipe racks, foundations - each project teaches you something new about how to catch problems before they become $50,000 change orders.&lt;/p&gt;

&lt;p&gt;Here's the workflow I use for every coordination review. This isn't theory. It's what works when you're responsible for making sure a 200-ton reactor platform doesn't fall over.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the Big Picture
&lt;/h2&gt;

&lt;p&gt;Before you look at a single beam detail, understand what you're building.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read these documents first:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Design basis memo&lt;/li&gt;
&lt;li&gt;Equipment data sheets&lt;/li&gt;
&lt;li&gt;Process flow diagrams&lt;/li&gt;
&lt;li&gt;Site survey&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You need context. Is this a new structure or a retrofit? What loads are we dealing with? I once caught a foundation design that used 100 psf live load when the actual equipment imposed 400 psf. The architect had copied specs from an office building.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three-Pass Review System
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pass 1: Layout and Geometry
&lt;/h3&gt;

&lt;p&gt;Open the plan views. Check these items in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Grid alignment&lt;/strong&gt; - Do structural grids match architectural grids?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Elevation consistency&lt;/strong&gt; - Are finished floor elevations the same across all drawings?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Equipment locations&lt;/strong&gt; - Do vessel centerlines match between P&amp;amp;ID and structural plans?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Create a simple checklist. I use a Python script that generates this for every project:&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;checklist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;grid_alignment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;elevation_match&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;equipment_centerlines&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;clearances_met&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;access_provided&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You fill it out as you go. Binary answers only - yes or no.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pass 2: Member Sizing and Connections
&lt;/h3&gt;

&lt;p&gt;Now you get into the structural details.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check these elements:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Beam depths against headroom requirements&lt;/li&gt;
&lt;li&gt;Connection details at equipment supports&lt;/li&gt;
&lt;li&gt;Foundation sizes vs equipment loads&lt;/li&gt;
&lt;li&gt;Seismic bracing locations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I keep a reference table of standard sizes we use:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Equipment Type&lt;/th&gt;
&lt;th&gt;Typical Beam&lt;/th&gt;
&lt;th&gt;Min Foundation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Horizontal vessel&lt;/td&gt;
&lt;td&gt;W18x35 min&lt;/td&gt;
&lt;td&gt;8'x8'x3'&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vertical pump&lt;/td&gt;
&lt;td&gt;W12x26 min&lt;/td&gt;
&lt;td&gt;6'x6'x2.5'&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heat exchanger&lt;/td&gt;
&lt;td&gt;W21x44 min&lt;/td&gt;
&lt;td&gt;10'x6'x3'&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These aren't code requirements. They're based on what actually works in the field.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pass 3: Constructability Review
&lt;/h3&gt;

&lt;p&gt;This is where experience matters most. You're looking for things that look right on paper but won't work with a crane and actual workers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Red flags I watch for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bolted connections you can't reach with a wrench&lt;/li&gt;
&lt;li&gt;Concrete pours that require impossible forming&lt;/li&gt;
&lt;li&gt;Steel members that interfere during erection sequence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I learned this the hard way on a platforming project in 2022. The drawing showed perfect bolt access. But the actual pipe routing made it impossible to get a socket wrench on 40% of the connections. We had to redesign three beam-to-column joints.&lt;/p&gt;

&lt;h2&gt;
  
  
  Documentation That Actually Helps
&lt;/h2&gt;

&lt;p&gt;Don't write novels in your review comments. Use this format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DWG: S-101
ITEM: Foundation F-3
ISSUE: Foundation 6'x6' but equipment data shows 8'x4' bolt pattern
ACTION: Resize foundation or confirm equipment dimensions
PRIORITY: HIGH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four lines maximum. If you can't explain the problem in four lines, you don't understand it well enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Coordination Failures
&lt;/h2&gt;

&lt;p&gt;These show up on 80% of the projects I review:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pipe rack column spacing&lt;/strong&gt; - Process wants 30' bays, structural used 25' modules. Someone has to give.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Equipment access&lt;/strong&gt; - The structure works but maintenance can't remove the pump impeller without cutting steel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Utility routing&lt;/strong&gt; - Perfect structural design that leaves nowhere to run the 4" cooling water line.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Foundation conflicts&lt;/strong&gt; - New foundation conflicts with existing underground utilities that weren't surveyed.&lt;/p&gt;

&lt;p&gt;I caught this last one three times in 2023. Each time saved at least two weeks of schedule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools That Speed Up Reviews
&lt;/h2&gt;

&lt;p&gt;I use RISA-3D for quick load path checks during review. If something looks questionable, I can model the critical members in 10 minutes and verify capacity.&lt;/p&gt;

&lt;p&gt;For large drawing sets, I wrote a Python script that extracts member sizes from PDFs and flags anything outside our standard size ranges:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_beam_sizes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;drawing_text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;beam_pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;W(\d+)x(\d+)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;beams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;beam_pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;drawing_text&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;depth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;beams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;flag_undersized_member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple automation that catches obvious problems.&lt;/p&gt;

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

&lt;p&gt;You're not just checking calculations. You're making sure someone can build this thing without calling you every day with problems.&lt;/p&gt;

&lt;p&gt;The best structural drawing review finds the issues that would stop construction. The worst one focuses on line weights and text fonts while missing the fact that the crane can't reach half the steel connections.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your next step:&lt;/strong&gt; Take your last drawing review and count how many comments were about constructability vs drafting standards. If it's less than 70% constructability, you're reviewing the wrong things.&lt;/p&gt;

&lt;p&gt;Focus on what breaks in the field, not what looks perfect on paper.&lt;/p&gt;

</description>
      <category>engineering</category>
      <category>career</category>
      <category>structural</category>
      <category>process</category>
    </item>
    <item>
      <title>How VAPI connects to Asterisk PJSIP for AI voice calls</title>
      <dc:creator>Domonique Luchin</dc:creator>
      <pubDate>Wed, 20 May 2026 10:00:02 +0000</pubDate>
      <link>https://dev.to/domoniqueluchin/how-vapi-connects-to-asterisk-pjsip-for-ai-voice-calls-b6d</link>
      <guid>https://dev.to/domoniqueluchin/how-vapi-connects-to-asterisk-pjsip-for-ai-voice-calls-b6d</guid>
      <description>&lt;p&gt;I run six AI-powered businesses from a single Vultr VPS. Each business needs phone capabilities. Instead of paying $50+ per month for hosted voice solutions, I built my own system using Asterisk and VAPI.&lt;/p&gt;

&lt;p&gt;Here's exactly how I connected VAPI to Asterisk PJSIP for AI voice calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with SaaS Voice Solutions
&lt;/h2&gt;

&lt;p&gt;Most AI voice platforms want you to use their phone numbers and infrastructure. You pay per minute, per call, per feature. For one business, maybe that works. For six businesses making hundreds of calls monthly, those costs add up fast.&lt;/p&gt;

&lt;p&gt;I needed control. I needed my own phone system that could handle AI agents without monthly subscriptions bleeding my profit margins.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Setup Overview
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Server&lt;/strong&gt;: Vultr VPS (4GB RAM, 2 vCPU)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PBX&lt;/strong&gt;: Asterisk 18 with PJSIP&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Voice&lt;/strong&gt;: VAPI agents&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SIP Provider&lt;/strong&gt;: Flowroute (you can use any)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monthly Cost&lt;/strong&gt;: $47 total (server + DID numbers + minutes)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This setup handles inbound and outbound AI calls for all six businesses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Asterisk PJSIP Configuration
&lt;/h2&gt;

&lt;p&gt;First, configure your PJSIP endpoints in &lt;code&gt;/etc/asterisk/pjsip.conf&lt;/code&gt;:&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;[transport-udp]&lt;/span&gt;
&lt;span class="py"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;transport&lt;/span&gt;
&lt;span class="py"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;udp&lt;/span&gt;
&lt;span class="py"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0:5060&lt;/span&gt;

&lt;span class="nn"&gt;[vapi-endpoint]&lt;/span&gt;
&lt;span class="py"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;endpoint&lt;/span&gt;
&lt;span class="py"&gt;context&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;vapi-context&lt;/span&gt;
&lt;span class="py"&gt;disallow&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;all&lt;/span&gt;
&lt;span class="py"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ulaw&lt;/span&gt;
&lt;span class="py"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;alaw&lt;/span&gt;
&lt;span class="py"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;vapi-auth&lt;/span&gt;
&lt;span class="py"&gt;aors&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;vapi-aor&lt;/span&gt;
&lt;span class="py"&gt;direct_media&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;no&lt;/span&gt;
&lt;span class="py"&gt;ice_support&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;yes&lt;/span&gt;
&lt;span class="py"&gt;media_encryption&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;sdes&lt;/span&gt;

&lt;span class="nn"&gt;[vapi-auth]&lt;/span&gt;
&lt;span class="py"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;auth&lt;/span&gt;
&lt;span class="py"&gt;auth_type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;userpass&lt;/span&gt;
&lt;span class="py"&gt;username&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;vapi-user&lt;/span&gt;
&lt;span class="py"&gt;password&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;your-secure-password&lt;/span&gt;

&lt;span class="nn"&gt;[vapi-aor]&lt;/span&gt;
&lt;span class="py"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;aor&lt;/span&gt;
&lt;span class="py"&gt;max_contacts&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;5&lt;/span&gt;
&lt;span class="py"&gt;remove_existing&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Dialplan for AI Call Routing
&lt;/h2&gt;

&lt;p&gt;Your dialplan (&lt;code&gt;/etc/asterisk/extensions.conf&lt;/code&gt;) routes calls to VAPI:&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;[vapi-context]&lt;/span&gt;
&lt;span class="py"&gt;exten&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;gt; _X.,1,NoOp(Incoming call for AI: ${CALLERID(num)})&lt;/span&gt;
&lt;span class="py"&gt;same&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;gt; n,Set(CHANNEL(hangup_handler_wipe)=vapi-cleanup,s,1)&lt;/span&gt;
&lt;span class="py"&gt;same&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;gt; n,Dial(PJSIP/vapi-endpoint,30)&lt;/span&gt;
&lt;span class="py"&gt;same&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;gt; n,Hangup()&lt;/span&gt;

&lt;span class="nn"&gt;[vapi-cleanup]&lt;/span&gt;
&lt;span class="py"&gt;exten&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;gt; s,1,NoOp(Call ended, cleanup complete)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For outbound calls through your SIP provider:&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;[outbound-context]&lt;/span&gt;
&lt;span class="py"&gt;exten&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;gt; _1NXXNXXXXXX,1,NoOp(Outbound call to: ${EXTEN})&lt;/span&gt;
&lt;span class="py"&gt;same&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;gt; n,Dial(PJSIP/${EXTEN}@flowroute-trunk,60)&lt;/span&gt;
&lt;span class="py"&gt;same&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;gt; n,Hangup()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  VAPI Integration Code
&lt;/h2&gt;

&lt;p&gt;VAPI needs to register with your Asterisk server as a SIP client. Here's the Python code I use to manage the connection:&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;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&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;create_vapi_phone_number&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.vapi.ai/phone-number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;provider&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;byom&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;byomPhoneNumber&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="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="s"&gt;your-asterisk-server.com&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;username&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;vapi-user&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;password&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;your-secure-password&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;assistantId&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;your-assistant-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;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;VAPI_API_KEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&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;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Configure your AI assistant
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setup_vapi_assistant&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.vapi.ai/assistant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;provider&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;openai&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;model&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;gpt-4&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;temperature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;voice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;provider&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;eleven-labs&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;voiceId&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;your-voice-id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;firstMessage&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;Hello, how can I help you today?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;VAPI_API_KEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&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;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Connection Flow
&lt;/h2&gt;

&lt;p&gt;Here's how the pieces connect:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Inbound Call&lt;/strong&gt;: Customer dials your number → SIP provider → Asterisk → VAPI agent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Outbound Call&lt;/strong&gt;: VAPI agent → Asterisk → SIP provider → Customer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audio Processing&lt;/strong&gt;: Real-time audio flows between caller and AI through PJSIP channels&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Configuration Gotchas
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Audio Codec Issues&lt;/strong&gt;: VAPI works best with ulaw and alaw. Don't enable high-definition codecs unless you want choppy AI responses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NAT Problems&lt;/strong&gt;: If your Asterisk server is behind NAT, add these lines to &lt;code&gt;pjsip.conf&lt;/code&gt;:&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;[global]&lt;/span&gt;
&lt;span class="py"&gt;external_media_address&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;your-public-ip&lt;/span&gt;
&lt;span class="py"&gt;external_signaling_address&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;your-public-ip&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Firewall Rules&lt;/strong&gt;: Open UDP port 5060 for SIP signaling and UDP ports 10000-20000 for RTP audio.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Performance Numbers
&lt;/h2&gt;

&lt;p&gt;My setup handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;12 concurrent AI calls without issues&lt;/li&gt;
&lt;li&gt;Average call setup time: 2.3 seconds&lt;/li&gt;
&lt;li&gt;Audio latency: 150-200ms (includes AI processing)&lt;/li&gt;
&lt;li&gt;Monthly costs: $47 for unlimited calls within my limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compare that to hosted solutions charging $0.10+ per minute for AI calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;You control your phone infrastructure. No vendor lock-in. No surprise billing. No rate limits beyond your hardware capacity.&lt;/p&gt;

&lt;p&gt;When one of my businesses needs a new phone number or different call routing, I make the change in my dialplan. Takes 5 minutes. No support tickets or billing adjustments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;Start with a basic Asterisk installation on a VPS. Get PJSIP working with a simple SIP provider first. Then add VAPI integration once your PBX handles regular calls properly.&lt;/p&gt;

&lt;p&gt;You'll spend a weekend learning Asterisk configuration. But you'll save hundreds monthly and own your communication stack completely.&lt;/p&gt;

&lt;p&gt;Want the complete configuration files? I've documented my entire setup at [your documentation link]. Stop paying monthly fees for infrastructure you can own.&lt;/p&gt;

</description>
      <category>voip</category>
      <category>ai</category>
      <category>selfhosted</category>
      <category>asterisk</category>
    </item>
    <item>
      <title>How I use RISA-3D to model equipment platform loads from a P&amp;ID</title>
      <dc:creator>Domonique Luchin</dc:creator>
      <pubDate>Tue, 19 May 2026 10:00:06 +0000</pubDate>
      <link>https://dev.to/domoniqueluchin/how-i-use-risa-3d-to-model-equipment-platform-loads-from-a-pid-1m9m</link>
      <guid>https://dev.to/domoniqueluchin/how-i-use-risa-3d-to-model-equipment-platform-loads-from-a-pid-1m9m</guid>
      <description>&lt;h1&gt;
  
  
  How I use RISA-3D to model equipment platform loads from a P&amp;amp;ID
&lt;/h1&gt;

&lt;p&gt;P&amp;amp;IDs contain the structural loads you need. You just have to know how to extract them.&lt;/p&gt;

&lt;p&gt;After 6 years designing pipe racks and equipment platforms in oil &amp;amp; gas, I've learned that the most critical step happens before you even open RISA-3D. You need to decode the P&amp;amp;ID properly. Miss a pump or underestimate a vessel load, and your platform design fails.&lt;/p&gt;

&lt;p&gt;Here's my workflow for turning P&amp;amp;ID symbols into accurate structural models.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Create your equipment inventory
&lt;/h2&gt;

&lt;p&gt;I start with a spreadsheet. Every piece of equipment gets catalogued:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tag Number&lt;/th&gt;
&lt;th&gt;Equipment Type&lt;/th&gt;
&lt;th&gt;Operating Weight (lbs)&lt;/th&gt;
&lt;th&gt;Dimensions (ft)&lt;/th&gt;
&lt;th&gt;Elevation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;P-101A/B&lt;/td&gt;
&lt;td&gt;Centrifugal Pump&lt;/td&gt;
&lt;td&gt;2,400&lt;/td&gt;
&lt;td&gt;6 x 3 x 4&lt;/td&gt;
&lt;td&gt;El. 115'-0"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;V-201&lt;/td&gt;
&lt;td&gt;Separator Vessel&lt;/td&gt;
&lt;td&gt;18,500&lt;/td&gt;
&lt;td&gt;12 dia x 25&lt;/td&gt;
&lt;td&gt;El. 120'-0"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E-301&lt;/td&gt;
&lt;td&gt;Heat Exchanger&lt;/td&gt;
&lt;td&gt;8,200&lt;/td&gt;
&lt;td&gt;4 x 12 x 6&lt;/td&gt;
&lt;td&gt;El. 118'-0"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The P&amp;amp;ID shows you what's there. Equipment data sheets give you the weights. Don't guess these numbers. I've seen platforms fail because someone estimated a 15,000 lb vessel at 10,000 lbs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Identify load paths
&lt;/h2&gt;

&lt;p&gt;Equipment doesn't float. Every piece needs structural support. I trace each load path from equipment to foundation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Direct bearing&lt;/strong&gt;: Heavy vessels typically bear directly on steel beams&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vibrating equipment&lt;/strong&gt;: Pumps and compressors need isolation springs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Piping loads&lt;/strong&gt;: Large bore piping creates significant lateral forces&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintenance access&lt;/strong&gt;: Platforms need live load capacity for personnel and lifting equipment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your P&amp;amp;ID won't show you this. You need to visualize the 3D arrangement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Set up the RISA-3D model
&lt;/h2&gt;

&lt;p&gt;I build my platform model in this sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Create main structural grid
2. Define beam sections (W12x40 typical for equipment beams)
3. Add columns and bracing
4. Apply boundary conditions
5. Define load combinations per AISC 360
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For equipment platforms, I use these typical member sizes as starting points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Equipment beams&lt;/strong&gt;: W12x40 minimum&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Framing beams&lt;/strong&gt;: W10x26&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Columns&lt;/strong&gt;: W8x35 or HSS12x12x1/2&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bracing&lt;/strong&gt;: HSS6x6x5/16&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 4: Apply equipment loads
&lt;/h2&gt;

&lt;p&gt;This is where the P&amp;amp;ID analysis pays off. In RISA-3D, I model equipment loads as point loads with these considerations:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Static loads (Dead Load)&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Equipment operating weight + piping + insulation + misc. steel
Factor: 1.2 (LRFD)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Dynamic loads (Live Load)&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Maintenance personnel: 125 psf
Equipment removal: 1.5 x equipment weight
Piping expansion forces: from stress analysis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the separator vessel example above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dead load: 18,500 lbs operating + 2,000 lbs piping = 20,500 lbs&lt;/li&gt;
&lt;li&gt;Live load: 27,750 lbs for removal case (1.5 x 18,500)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I apply these as nodal loads in RISA-3D at the actual equipment support points.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Model piping restraint loads
&lt;/h2&gt;

&lt;p&gt;P&amp;amp;IDs show pipe routing but not restraint loads. You need to coordinate with the piping stress engineer. I typically see these magnitudes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Anchor loads&lt;/strong&gt;: 5,000 to 50,000 lbs depending on line size and pressure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guide loads&lt;/strong&gt;: 1,000 to 10,000 lbs lateral&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spring hanger loads&lt;/strong&gt;: Variable based on supported pipe weight&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In RISA-3D, I model these as applied loads at beam locations where pipe supports attach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Check your work
&lt;/h2&gt;

&lt;p&gt;Before running analysis, I verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Total platform dead load matches equipment inventory ± 10%&lt;/li&gt;
&lt;li&gt;Load paths are continuous to foundations&lt;/li&gt;
&lt;li&gt;All equipment has adequate support points&lt;/li&gt;
&lt;li&gt;Live load combinations include maintenance scenarios&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Common mistakes I catch here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Forgetting piping loads (can be 30% of total load)&lt;/li&gt;
&lt;li&gt;Underestimating access platform requirements&lt;/li&gt;
&lt;li&gt;Missing thermal expansion effects&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 7: Analyze and iterate
&lt;/h2&gt;

&lt;p&gt;RISA-3D gives you member utilization ratios. For equipment platforms, I target:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Beams&lt;/strong&gt;: 85% maximum utilization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Columns&lt;/strong&gt;: 80% maximum utilization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deflections&lt;/strong&gt;: L/240 for equipment beams&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If members are overstressed, I resize and reanalyze. The P&amp;amp;ID constraints are fixed. Your structure must accommodate them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real project example
&lt;/h2&gt;

&lt;p&gt;Last month I designed a platform for three centrifugal pumps. The P&amp;amp;ID showed simple pump symbols. The reality:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each pump: 3,200 lbs + 1,800 lbs piping&lt;/li&gt;
&lt;li&gt;Suction/discharge forces: 8,500 lbs combined&lt;/li&gt;
&lt;li&gt;Maintenance crane load: 12,000 lbs&lt;/li&gt;
&lt;li&gt;Platform size: 20' x 30'&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total design load: 67,000 lbs on a platform that would carry 15,000 lbs if I'd only considered pump weights.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your next step
&lt;/h2&gt;

&lt;p&gt;Start with your current project P&amp;amp;ID. List every piece of equipment. Get the actual weights from data sheets, not estimates. Build your equipment inventory spreadsheet before you touch RISA-3D.&lt;/p&gt;

&lt;p&gt;The P&amp;amp;ID tells you what to design for. RISA-3D tells you if your design works. Master the translation between them, and your platforms will stand.&lt;/p&gt;

</description>
      <category>engineering</category>
      <category>structural</category>
      <category>software</category>
      <category>career</category>
    </item>
    <item>
      <title>Zero-downtime deployments on a single Vultr VPS with Docker and Nginx</title>
      <dc:creator>Domonique Luchin</dc:creator>
      <pubDate>Mon, 18 May 2026 10:00:04 +0000</pubDate>
      <link>https://dev.to/domoniqueluchin/zero-downtime-deployments-on-a-single-vultr-vps-with-docker-and-nginx-3if7</link>
      <guid>https://dev.to/domoniqueluchin/zero-downtime-deployments-on-a-single-vultr-vps-with-docker-and-nginx-3if7</guid>
      <description>&lt;p&gt;I run six AI-powered businesses from a single Vultr VPS. When I deploy updates to my VAPI agents or Supabase integrations, downtime costs me real money. Customers calling my AI phone systems expect 24/7 availability.&lt;/p&gt;

&lt;p&gt;Here's how I achieve zero-downtime deployments using Docker and Nginx on one $40/month server.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Basic Deployments
&lt;/h2&gt;

&lt;p&gt;Most developers stop their container, pull new code, rebuild, and restart. This creates 30-60 seconds of downtime per deployment. For my Load Bearing Empire businesses, that's unacceptable.&lt;/p&gt;

&lt;p&gt;Your users hit 502 errors. Your monitoring tools send alerts. You lose credibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Zero-Downtime Setup
&lt;/h2&gt;

&lt;p&gt;I use Docker Compose with multiple container instances behind Nginx. During deployments, I spin up new containers before stopping old ones.&lt;/p&gt;

&lt;p&gt;Here's my production structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/opt/apps/
├── business-app/
│   ├── docker-compose.yml
│   ├── nginx.conf
│   └── deploy.sh
└── nginx/
    └── sites-enabled/
        └── business-app.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Docker Compose Configuration
&lt;/h2&gt;

&lt;p&gt;My &lt;code&gt;docker-compose.yml&lt;/code&gt; runs two instances of each service:&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;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.8'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app-blue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;business-app-blue&lt;/span&gt;
    &lt;span class="na"&gt;ports&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;3001:3000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NODE_ENV=production&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

  &lt;span class="na"&gt;app-green&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;business-app-green&lt;/span&gt;
    &lt;span class="na"&gt;ports&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;3002:3000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NODE_ENV=production&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I call this blue-green deployment. One container serves traffic while the other stays ready for updates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nginx Load Balancer Setup
&lt;/h2&gt;

&lt;p&gt;Nginx distributes requests between containers. Here's my &lt;code&gt;/etc/nginx/sites-enabled/business-app.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;upstream&lt;/span&gt; &lt;span class="s"&gt;business_app&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="nf"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3001&lt;/span&gt; &lt;span class="s"&gt;weight=100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="nf"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3002&lt;/span&gt; &lt;span class="s"&gt;weight=0&lt;/span&gt; &lt;span class="s"&gt;backup&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;yourdomain.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://business_app&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_http_version&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Upgrade&lt;/span&gt; &lt;span class="nv"&gt;$http_upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Connection&lt;/span&gt; &lt;span class="s"&gt;'upgrade'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Real-IP&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_cache_bypass&lt;/span&gt; &lt;span class="nv"&gt;$http_upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;weight=0&lt;/code&gt; setting keeps the green container as backup. During deployments, I flip these weights.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Deployment Script
&lt;/h2&gt;

&lt;p&gt;My &lt;code&gt;deploy.sh&lt;/code&gt; script handles the entire zero-downtime process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;ACTIVE_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost/health | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s1"&gt;'"port":[0-9]*'&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;: &lt;span class="nt"&gt;-f2&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ACTIVE_PORT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"3001"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;DEPLOY_CONTAINER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"app-green"&lt;/span&gt;
    &lt;span class="nv"&gt;DEPLOY_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"3002"&lt;/span&gt;
    &lt;span class="nv"&gt;ACTIVE_WEIGHT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"weight=100"&lt;/span&gt;
    &lt;span class="nv"&gt;DEPLOY_WEIGHT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"weight=100"&lt;/span&gt;
&lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nv"&gt;DEPLOY_CONTAINER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"app-blue"&lt;/span&gt;  
    &lt;span class="nv"&gt;DEPLOY_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"3001"&lt;/span&gt;
    &lt;span class="nv"&gt;ACTIVE_WEIGHT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"weight=100"&lt;/span&gt;
    &lt;span class="nv"&gt;DEPLOY_WEIGHT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"weight=100"&lt;/span&gt;
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Deploying to &lt;/span&gt;&lt;span class="nv"&gt;$DEPLOY_CONTAINER&lt;/span&gt;&lt;span class="s2"&gt; on port &lt;/span&gt;&lt;span class="nv"&gt;$DEPLOY_PORT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Pull latest code and rebuild&lt;/span&gt;
git pull origin main
docker-compose build &lt;span class="nv"&gt;$DEPLOY_CONTAINER&lt;/span&gt;
docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;$DEPLOY_CONTAINER&lt;/span&gt;

&lt;span class="c"&gt;# Wait for container health check&lt;/span&gt;
&lt;span class="nb"&gt;sleep &lt;/span&gt;10
curl &lt;span class="nt"&gt;-f&lt;/span&gt; http://localhost:&lt;span class="nv"&gt;$DEPLOY_PORT&lt;/span&gt;/health &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1

&lt;span class="c"&gt;# Update Nginx upstream weights&lt;/span&gt;
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"s/server localhost:&lt;/span&gt;&lt;span class="nv"&gt;$DEPLOY_PORT&lt;/span&gt;&lt;span class="s2"&gt; weight=[0-9]*/server localhost:&lt;/span&gt;&lt;span class="nv"&gt;$DEPLOY_PORT&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$DEPLOY_WEIGHT&lt;/span&gt;&lt;span class="s2"&gt;/"&lt;/span&gt; /etc/nginx/sites-enabled/business-app.conf

&lt;span class="c"&gt;# Reload Nginx (zero downtime)&lt;/span&gt;
nginx &lt;span class="nt"&gt;-s&lt;/span&gt; reload

&lt;span class="c"&gt;# Wait 30 seconds for connections to drain&lt;/span&gt;
&lt;span class="nb"&gt;sleep &lt;/span&gt;30

&lt;span class="c"&gt;# Set old container as backup&lt;/span&gt;
&lt;span class="nv"&gt;OLD_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;&lt;span class="m"&gt;3003&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$DEPLOY_PORT&lt;/span&gt;&lt;span class="k"&gt;))&lt;/span&gt;
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"s/server localhost:&lt;/span&gt;&lt;span class="nv"&gt;$OLD_PORT&lt;/span&gt;&lt;span class="s2"&gt; weight=[0-9]*/server localhost:&lt;/span&gt;&lt;span class="nv"&gt;$OLD_PORT&lt;/span&gt;&lt;span class="s2"&gt; weight=0 backup/"&lt;/span&gt; /etc/nginx/sites-enabled/business-app.conf

nginx &lt;span class="nt"&gt;-s&lt;/span&gt; reload
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Deployment complete"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Health Checks Are Critical
&lt;/h2&gt;

&lt;p&gt;Your application needs a &lt;code&gt;/health&lt;/code&gt; endpoint. Mine returns the container port and timestamp:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/health&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;healthy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The deployment script uses this endpoint to verify the new container works before switching traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Database Migration Strategy
&lt;/h2&gt;

&lt;p&gt;For database changes, I run migrations before deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run migrations first&lt;/span&gt;
npm run migrate:up

&lt;span class="c"&gt;# Then deploy application&lt;/span&gt;
./deploy.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works because I design backward-compatible migrations. New columns get default values. Old columns stay until the next deployment cycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring Your Deployments
&lt;/h2&gt;

&lt;p&gt;I monitor deployment success with simple curl commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check both containers respond&lt;/span&gt;
curl &lt;span class="nt"&gt;-f&lt;/span&gt; http://localhost:3001/health
curl &lt;span class="nt"&gt;-f&lt;/span&gt; http://localhost:3002/health

&lt;span class="c"&gt;# Monitor response times&lt;/span&gt;
curl &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"@curl-format.txt"&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null http://yourdomain.com/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My &lt;code&gt;curl-format.txt&lt;/code&gt; shows timing breakdown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;time_namelookup&lt;/span&gt;:  %{&lt;span class="n"&gt;time_namelookup&lt;/span&gt;}\&lt;span class="n"&gt;n&lt;/span&gt;
&lt;span class="n"&gt;time_connect&lt;/span&gt;:     %{&lt;span class="n"&gt;time_connect&lt;/span&gt;}\&lt;span class="n"&gt;n&lt;/span&gt;
&lt;span class="n"&gt;time_appconnect&lt;/span&gt;:  %{&lt;span class="n"&gt;time_appconnect&lt;/span&gt;}\&lt;span class="n"&gt;n&lt;/span&gt;
&lt;span class="n"&gt;time_total&lt;/span&gt;:       %{&lt;span class="n"&gt;time_total&lt;/span&gt;}\&lt;span class="n"&gt;n&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Resource Usage Reality Check
&lt;/h2&gt;

&lt;p&gt;This setup uses about 2GB RAM for two Node.js containers plus Nginx on my 4GB Vultr VPS. CPU usage stays under 20% during deployments.&lt;/p&gt;

&lt;p&gt;Your mileage varies based on application size and deployment frequency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Beats Kubernetes
&lt;/h2&gt;

&lt;p&gt;Kubernetes offers similar capabilities but requires 3+ nodes for true high availability. That's $120+ monthly versus my $40 VPS.&lt;/p&gt;

&lt;p&gt;For small businesses, this Docker approach provides 99.9% uptime without the complexity tax.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your Next Steps
&lt;/h2&gt;

&lt;p&gt;Start with health checks in your existing application. Add the &lt;code&gt;/health&lt;/code&gt; endpoint this week. Set up the blue-green containers next weekend. Deploy the Nginx configuration after testing locally.&lt;/p&gt;

&lt;p&gt;You'll sleep better knowing your deployments don't wake up angry customers.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>docker</category>
      <category>selfhosted</category>
      <category>cloud</category>
    </item>
    <item>
      <title>How I use Claude Code to dispatch agent tasks from a Supabase queue table</title>
      <dc:creator>Domonique Luchin</dc:creator>
      <pubDate>Wed, 13 May 2026 10:00:02 +0000</pubDate>
      <link>https://dev.to/domoniqueluchin/how-i-use-claude-code-to-dispatch-agent-tasks-from-a-supabase-queue-table-29jp</link>
      <guid>https://dev.to/domoniqueluchin/how-i-use-claude-code-to-dispatch-agent-tasks-from-a-supabase-queue-table-29jp</guid>
      <description>&lt;p&gt;I run 6 AI-powered businesses from a single Vultr VPS. Each business needs different types of agents - some handle customer calls, others process documents, and a few manage data workflows.&lt;/p&gt;

&lt;p&gt;The challenge? Coordinating all these agents without chaos.&lt;/p&gt;

&lt;p&gt;My solution: a Supabase queue table that feeds tasks to Claude-powered dispatchers. Here's exactly how I built it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Queue Table Structure
&lt;/h2&gt;

&lt;p&gt;First, I created a simple queue table in Supabase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;task_queue&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;business_id&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;task_type&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="n"&gt;JSONB&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'pending'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;priority&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;assigned_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;completed_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_queue_status_priority&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;task_queue&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="n"&gt;priority&lt;/span&gt; &lt;span class="k"&gt;DESC&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;business_id&lt;/code&gt; field maps to my 6 different companies. Task types include "customer_call", "document_review", "data_sync", and "lead_qualification".&lt;/p&gt;

&lt;h2&gt;
  
  
  Claude as the Dispatcher
&lt;/h2&gt;

&lt;p&gt;I use Claude's structured output to analyze incoming tasks and assign them to the right agents. Here's my dispatcher function:&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;supabase&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;create_client&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Anthropic&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TaskDispatcher&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;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;supabase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SUPABASE_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SUPABASE_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;claude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;CLAUDE_API_KEY&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;process_queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Get pending tasks ordered by priority
&lt;/span&gt;        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;task_queue&lt;/span&gt;&lt;span class="sh"&gt;'&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;*&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;eq&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pending&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;priority&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;desc&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="nf"&gt;limit&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="nf"&gt;execute&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;task&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispatch_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&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;dispatch_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Use Claude to determine the best agent
&lt;/span&gt;        &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        Analyze this task and determine the optimal agent assignment:

        Business: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;business_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
        Task Type: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;task_type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
        Payload: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;payload&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;

        Return JSON with:
        - agent_type: specific agent category
        - estimated_duration: minutes
        - requirements: list of needed resources
        &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;claude&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-3-sonnet-20240229&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&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;user&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Parse Claude's response and route accordingly
&lt;/span&gt;        &lt;span class="n"&gt;routing_info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&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;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign_to_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;routing_info&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Agent Assignment Logic
&lt;/h2&gt;

&lt;p&gt;Claude doesn't just pick agents randomly. I trained it on my specific business requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real Estate agents&lt;/strong&gt;: Handle property inquiries, schedule showings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Construction agents&lt;/strong&gt;: Process RFPs, manage vendor communications
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document agents&lt;/strong&gt;: Review contracts, extract key data points&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Call agents&lt;/strong&gt;: Handle inbound sales, customer support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The dispatcher updates the queue table with assignment details:&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="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;assign_to_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;routing_info&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Mark task as assigned
&lt;/span&gt;    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;task_queue&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;assigned&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;assigned_at&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;NOW()&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;agent_type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;routing_info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;agent_type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;estimated_duration&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;routing_info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;estimated_duration&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;eq&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;task&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="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Send to appropriate agent queue
&lt;/span&gt;    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;route_to_agent_system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;routing_info&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Real Performance Numbers
&lt;/h2&gt;

&lt;p&gt;After 3 months of running this system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Average task assignment time&lt;/strong&gt;: 2.3 seconds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Queue processing rate&lt;/strong&gt;: 150 tasks per minute&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent utilization&lt;/strong&gt;: 78% (up from 45% with manual routing)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failed assignments&lt;/strong&gt;: 0.8%&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key insight: Claude excels at understanding context and making routing decisions that would take me hours to code manually.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling Task Failures
&lt;/h2&gt;

&lt;p&gt;Not every agent completes their task successfully. My system automatically retries failed tasks:&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="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_failed_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error_reason&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Get current task
&lt;/span&gt;    &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;task_queue&lt;/span&gt;&lt;span class="sh"&gt;'&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;*&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;eq&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;task_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;single&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Use Claude to analyze the failure
&lt;/span&gt;    &lt;span class="n"&gt;analysis_prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Task failed with error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;error_reason&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
    Original task: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;

    Should we:
    1. Retry with same agent
    2. Reassign to different agent type
    3. Mark as failed and alert human

    Provide reasoning and recommendation.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="c1"&gt;# Get Claude's recommendation and act accordingly
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This failure analysis catches 90% of issues before they need human intervention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost Breakdown
&lt;/h2&gt;

&lt;p&gt;Running this dispatcher costs me $23 per month:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude API calls: $18&lt;/li&gt;
&lt;li&gt;Supabase compute: $5&lt;/li&gt;
&lt;li&gt;VPS overhead: $0 (shared with other services)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compare that to enterprise task management platforms at $200+ monthly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your Next Steps
&lt;/h2&gt;

&lt;p&gt;Start simple. Create a basic queue table in Supabase and connect it to Claude for task analysis. You don't need 6 businesses to benefit from intelligent task routing.&lt;/p&gt;

&lt;p&gt;The real power comes from teaching Claude your specific business logic through examples and clear prompts. Your agents will work smarter, not just harder.&lt;/p&gt;

&lt;p&gt;What tasks are you manually routing that Claude could handle better?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>supabase</category>
      <category>python</category>
    </item>
    <item>
      <title>Why I chose LangGraph over CrewAI for multi-agent orchestration</title>
      <dc:creator>Domonique Luchin</dc:creator>
      <pubDate>Tue, 12 May 2026 10:00:03 +0000</pubDate>
      <link>https://dev.to/domoniqueluchin/why-i-chose-langgraph-over-crewai-for-multi-agent-orchestration-2b5h</link>
      <guid>https://dev.to/domoniqueluchin/why-i-chose-langgraph-over-crewai-for-multi-agent-orchestration-2b5h</guid>
      <description>&lt;p&gt;I needed to solve a real problem: orchestrating six different AI agents across my real estate and engineering businesses without relying on expensive SaaS platforms.&lt;/p&gt;

&lt;p&gt;Each business in my Load Bearing Empire requires different agent behaviors. My property management service needs agents that handle tenant inquiries and maintenance scheduling. My structural engineering consultancy needs agents that process RFIs and coordinate with project teams. My lead generation service needs agents that qualify prospects and book appointments.&lt;/p&gt;

&lt;p&gt;CrewAI looked appealing at first. The framework promises simple multi-agent workflows with minimal setup. But when I dug deeper, I found limitations that would hurt my infrastructure-first approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  The control problem
&lt;/h2&gt;

&lt;p&gt;CrewAI abstracts away too much of the orchestration logic. You define agents and tasks, but the framework decides how they interact. Here's a typical CrewAI setup:&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;from&lt;/span&gt; &lt;span class="n"&gt;crewai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Crew&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;

&lt;span class="n"&gt;agent1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;researcher&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;goal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Find information&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;agent2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;writer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;goal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Create content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Research and write about topic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;crew&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Crew&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;agent1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agent2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;crew&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;kickoff&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works for simple cases. But what happens when you need conditional logic? What if Agent A should only talk to Agent B under specific circumstances? What if you need to implement custom retry logic or error handling between agents?&lt;/p&gt;

&lt;p&gt;CrewAI gives you limited options. You're stuck with their predefined interaction patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  LangGraph's state-first approach
&lt;/h2&gt;

&lt;p&gt;LangGraph treats multi-agent orchestration as a state management problem. You define a graph where each node represents a decision point or action. Agents become functions that modify shared state.&lt;/p&gt;

&lt;p&gt;Here's how I structure my property management workflow:&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;from&lt;/span&gt; &lt;span class="n"&gt;langgraph.graph&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;StateGraph&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TypedDict&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AgentState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TypedDict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;inquiry_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;priority_level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;assigned_agent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;response_ready&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;classify_inquiry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AgentState&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;AgentState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Classification logic here
&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;inquiry_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maintenance&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;priority_level&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&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;route_to_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AgentState&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;str&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;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;priority_level&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="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;emergency_agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;standard_agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;workflow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;StateGraph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AgentState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;classifier&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;classify_inquiry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;emergency_agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handle_emergency&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;standard_agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handle_standard&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_conditional_edges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;classifier&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;route_to_agent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You control exactly how agents interact. You decide when state gets passed between nodes. You implement your own routing logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance on limited resources
&lt;/h2&gt;

&lt;p&gt;I run everything on a single Vultr VPS with 8GB RAM. Resource efficiency matters.&lt;/p&gt;

&lt;p&gt;CrewAI spawns separate processes for each agent by default. With six businesses running different agent combinations, this quickly consumes memory. I measured average memory usage of 400-600MB per CrewAI crew during peak loads.&lt;/p&gt;

&lt;p&gt;LangGraph agents share the same Python process. State gets passed as dictionaries between functions. My entire multi-agent system uses 150-200MB of memory during the same workloads.&lt;/p&gt;

&lt;p&gt;The difference compounds when you're running multiple workflows simultaneously.&lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging and observability
&lt;/h2&gt;

&lt;p&gt;When something breaks in CrewAI, you get limited visibility into the agent interactions. The framework handles message passing internally. You see the final result, but troubleshooting intermediate steps requires diving into CrewAI's logging system.&lt;/p&gt;

&lt;p&gt;LangGraph workflows are just Python functions. You can add print statements, logging, or custom monitoring at any step:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;maintenance_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AgentState&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;AgentState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Processing ticket &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;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;tenant_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; - Priority &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;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;priority_level&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Your agent logic here
&lt;/span&gt;    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Log the result
&lt;/span&gt;    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Response generated: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; characters&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;response_ready&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;response&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You own the entire execution path. No black box abstractions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The infrastructure angle
&lt;/h2&gt;

&lt;p&gt;CrewAI encourages you to use their cloud platform for advanced features like agent memory and task persistence. This goes against my philosophy of owning your infrastructure.&lt;/p&gt;

&lt;p&gt;LangGraph integrates cleanly with any database. I store agent state in my existing Supabase instance. Workflow history, agent performance metrics, and business logic all live in systems I control.&lt;/p&gt;

&lt;p&gt;Here's my state persistence setup:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;save_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AgentState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;workflow_id&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;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent_workflows&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;workflow_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;workflow_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;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;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;execute&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;load_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workflow_id&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="n"&gt;AgentState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent_workflows&lt;/span&gt;&lt;span class="sh"&gt;"&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;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;workflow_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;workflow_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No vendor lock-in. No monthly fees for basic functionality.&lt;/p&gt;

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

&lt;p&gt;LangGraph requires more upfront thinking about your workflows. You write more code than CrewAI's declarative approach. But you get precise control over agent interactions, better resource utilization, and complete ownership of your orchestration logic.&lt;/p&gt;

&lt;p&gt;For my use case - multiple businesses running on shared infrastructure with custom requirements - LangGraph was the clear choice.&lt;/p&gt;

&lt;p&gt;If you're building multi-agent systems and want to maintain control over your infrastructure, start with LangGraph's state management approach. Your future self will thank you when you need to customize agent behavior beyond what frameworks allow.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>langgraph</category>
      <category>agents</category>
    </item>
    <item>
      <title>Building a self-healing cron system with pg_cron and Supabase edge functions</title>
      <dc:creator>Domonique Luchin</dc:creator>
      <pubDate>Mon, 11 May 2026 10:00:02 +0000</pubDate>
      <link>https://dev.to/domoniqueluchin/building-a-self-healing-cron-system-with-pgcron-and-supabase-edge-functions-5420</link>
      <guid>https://dev.to/domoniqueluchin/building-a-self-healing-cron-system-with-pgcron-and-supabase-edge-functions-5420</guid>
      <description>&lt;p&gt;I run 6 AI businesses from a single VPS. When your entire operation depends on automated tasks running perfectly, you learn to build systems that fix themselves before you wake up to angry customers.&lt;/p&gt;

&lt;p&gt;Here's how I built a cron system that monitors itself and recovers from failures automatically using pg_cron and Supabase Edge Functions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I needed this
&lt;/h2&gt;

&lt;p&gt;My Load Bearing Empire processes thousands of AI agent calls daily. Lead scoring runs every 15 minutes. Data sync happens hourly. Payment processing triggers every 30 minutes. &lt;/p&gt;

&lt;p&gt;A single failed cron job costs me real money. I've been burned by silent failures too many times.&lt;/p&gt;

&lt;p&gt;Most developers rely on external monitoring services. I prefer owning my infrastructure. This system costs me $0 in additional subscriptions and runs entirely within Supabase.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;Three components work together:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;pg_cron&lt;/strong&gt; schedules and executes jobs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge Functions&lt;/strong&gt; handle the actual business logic
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health monitoring table&lt;/strong&gt; tracks job status and triggers recovery&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key insight: every cron job reports its status to a central monitoring table. If a job fails or doesn't report in, the system automatically retries and alerts me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the foundation
&lt;/h2&gt;

&lt;p&gt;First, enable pg_cron in your Supabase project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Run this in your SQL editor&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;EXTENSION&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;pg_cron&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Create the monitoring table&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;cron_health&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;job_name&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;last_run&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;last_success&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'running'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'success'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'failed'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="n"&gt;error_message&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;retry_count&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&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;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Index for fast lookups&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_cron_health_job_name&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;cron_health&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_cron_health_last_run&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;cron_health&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last_run&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating a self-reporting Edge Function
&lt;/h2&gt;

&lt;p&gt;Here's an Edge Function that reports its own health status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// supabase/functions/process-leads/index.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;serve&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://deno.land/std@0.168.0/http/server.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://esm.sh/@supabase/supabase-js@2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jobName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;process-leads&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SUPABASE_URL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SUPABASE_SERVICE_ROLE_KEY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Update status to running&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cron_health&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jobName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;last_run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;running&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;retry_count&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;onConflict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;job_name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c1"&gt;// Your actual business logic here&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;processLeads&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;// Report success&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cron_health&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jobName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;last_run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;last_success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;error_message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;onConflict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;job_name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;processed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt;

  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Report failure&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cron_health&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jobName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;last_run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;error_message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;retry_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getCurrentRetryCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jobName&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;onConflict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;job_name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The self-healing mechanism
&lt;/h2&gt;

&lt;p&gt;This monitoring function runs every 5 minutes and handles recovery:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Create the health check function&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;check_cron_health&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="n"&gt;void&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;DECLARE&lt;/span&gt;
  &lt;span class="n"&gt;job_record&lt;/span&gt; &lt;span class="n"&gt;RECORD&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;function_url&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
  &lt;span class="c1"&gt;-- Find jobs that haven't reported success in their expected interval&lt;/span&gt;
  &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="n"&gt;job_record&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; 
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;job_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;last_run&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;last_success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retry_count&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cron_health&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="c1"&gt;-- Jobs that should run every 15 minutes but haven't succeeded in 20 minutes&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_name&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%leads%'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;last_success&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'20 minutes'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt;
      &lt;span class="c1"&gt;-- Jobs that should run hourly but haven't succeeded in 75 minutes  &lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_name&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%sync%'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;last_success&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'75 minutes'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;retry_count&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
  &lt;span class="n"&gt;LOOP&lt;/span&gt;
    &lt;span class="c1"&gt;-- Build the Edge Function URL&lt;/span&gt;
    &lt;span class="n"&gt;function_url&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://your-project.supabase.co/functions/v1/'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;job_record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;job_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;-- Trigger retry via HTTP request&lt;/span&gt;
    &lt;span class="n"&gt;PERFORM&lt;/span&gt; &lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;http_post&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="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;function_url&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;=&lt;/span&gt; &lt;span class="s1"&gt;'{"Authorization": "Bearer '&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;current_setting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'app.service_role_key'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'"}'&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="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'{}'&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;-- Log the retry attempt&lt;/span&gt;
    &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;cron_health&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;last_run&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="n"&gt;retry_count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;job_name&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'_retry'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s1"&gt;'retry_triggered'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;job_record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retry_count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;LOOP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Scheduling everything
&lt;/h2&gt;

&lt;p&gt;Now wire it all together with pg_cron:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Schedule your business logic&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'process-leads'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'*/15 * * * *'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="s1"&gt;'SELECT net.http_post(&lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;https://your-project.supabase.co/functions/v1/process-leads&lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;, &lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;{"Authorization": "Bearer service_role_key"}&lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;, &lt;/span&gt;&lt;span class="se"&gt;''''&lt;/span&gt;&lt;span class="s1"&gt;)'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Schedule the health monitor&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'health-check'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'*/5 * * * *'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'SELECT check_cron_health()'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Clean up old health records weekly&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'cleanup-health'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'0 2 * * 0'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="s1"&gt;'DELETE FROM cron_health WHERE created_at &amp;lt; NOW() - INTERVAL &lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;30 days&lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Monitoring dashboard
&lt;/h2&gt;

&lt;p&gt;Query this to see your system health:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Current status of all jobs&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
  &lt;span class="n"&gt;job_name&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="n"&gt;last_success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EPOCH&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;last_success&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;minutes_since_success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;retry_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;error_message&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cron_health&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;job_name&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%retry%'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;last_run&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Real results
&lt;/h2&gt;

&lt;p&gt;Since implementing this system 3 months ago:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zero silent failures&lt;/li&gt;
&lt;li&gt;4 automatic recoveries from network timeouts&lt;/li&gt;
&lt;li&gt;99.8% job success rate&lt;/li&gt;
&lt;li&gt;2 minutes average recovery time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You get infrastructure that fixes itself. Your cron jobs report their health. Failed jobs retry automatically. You sleep better knowing your systems won't fail silently.&lt;/p&gt;

&lt;p&gt;Build systems that work without you watching them.&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>postgres</category>
      <category>devops</category>
      <category>automation</category>
    </item>
    <item>
      <title>Why I Own My Telecom Stack Instead of Paying $200/Month for VoIP SaaS</title>
      <dc:creator>Domonique Luchin</dc:creator>
      <pubDate>Fri, 08 May 2026 10:00:02 +0000</pubDate>
      <link>https://dev.to/domoniqueluchin/why-i-own-my-telecom-stack-instead-of-paying-200month-for-voip-saas-13ce</link>
      <guid>https://dev.to/domoniqueluchin/why-i-own-my-telecom-stack-instead-of-paying-200month-for-voip-saas-13ce</guid>
      <description>&lt;p&gt;Business phone systems are a tax on not knowing how VoIP works.&lt;/p&gt;

&lt;p&gt;I stopped paying it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Market Charges
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Grasshopper: $49/month for 1 number, 3 extensions&lt;/li&gt;
&lt;li&gt;RingCentral: $99/month per user&lt;/li&gt;
&lt;li&gt;Dialpad: $75/month per user&lt;/li&gt;
&lt;li&gt;Google Voice for Business: $30/month per user&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For six business lines with AI voice agents, I would spend $200 to $500 every month depending on the plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Actually Pay
&lt;/h2&gt;

&lt;p&gt;$29.70 per month. Six lines. AI on every one.&lt;/p&gt;

&lt;p&gt;Breakdown:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VoIP.ms DIDs: 6 x $4.95 = $29.70/month&lt;/li&gt;
&lt;li&gt;Asterisk: free, runs on a VPS I already own&lt;/li&gt;
&lt;li&gt;VAPI: usage-based, low volume in early stage&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I Own vs What I Rent
&lt;/h2&gt;

&lt;p&gt;With SaaS I rent access. If they raise prices, change terms, or shut down, I lose the system.&lt;/p&gt;

&lt;p&gt;With Asterisk + VoIP.ms I own the PBX configuration, own the SIP trunk relationship, and can move providers without rebuilding anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Infrastructure Sovereignty Principle
&lt;/h2&gt;

&lt;p&gt;I apply five rules to every service in my stack:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Multi-model abstracted — no single vendor lock-in&lt;/li&gt;
&lt;li&gt;Logged to Supabase in real time&lt;/li&gt;
&lt;li&gt;Self-hosted equivalent exists for every managed service&lt;/li&gt;
&lt;li&gt;Contingency defined before going live&lt;/li&gt;
&lt;li&gt;All data, prompts, and outputs are mine&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Telecom is not special. The same logic applies. Own the stack or pay the tax forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is It Hard?
&lt;/h2&gt;

&lt;p&gt;Asterisk has a learning curve. VoIP.ms requires KYC. PJSIP configuration takes a few hours to get right.&lt;/p&gt;

&lt;p&gt;After that, it runs without touching it. That is the trade. A few hard hours once, or $200 every month forever.&lt;/p&gt;

</description>
      <category>selfhosted</category>
      <category>voip</category>
      <category>devops</category>
      <category>entrepreneurship</category>
    </item>
    <item>
      <title>From Gulf Coast Pipe Racks to AI Agent Orchestration: The Engineer's Path</title>
      <dc:creator>Domonique Luchin</dc:creator>
      <pubDate>Thu, 07 May 2026 10:00:02 +0000</pubDate>
      <link>https://dev.to/domoniqueluchin/from-gulf-coast-pipe-racks-to-ai-agent-orchestration-the-engineers-path-120p</link>
      <guid>https://dev.to/domoniqueluchin/from-gulf-coast-pipe-racks-to-ai-agent-orchestration-the-engineers-path-120p</guid>
      <description>&lt;p&gt;I spent six years designing pipe racks and equipment platforms for Gulf Coast oil and gas facilities.&lt;/p&gt;

&lt;p&gt;Now I also build AI agent systems that run six businesses.&lt;/p&gt;

&lt;p&gt;These two things are not as different as they sound.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structural Engineering as a Mental Model
&lt;/h2&gt;

&lt;p&gt;Structural engineering teaches you to think in systems. Every element has a load path. Every load path has a failure mode. You design for the failure mode before you design for the load.&lt;/p&gt;

&lt;p&gt;AI agent architecture works the same way.&lt;/p&gt;

&lt;p&gt;Every agent has an input path. Every input path has a failure mode — hallucination, timeout, bad routing, context loss. You design the guardrails before you design the workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Engineering Gave Me
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Tolerance for specification-heavy documentation&lt;/li&gt;
&lt;li&gt;Comfort with systems that have real consequences if they fail&lt;/li&gt;
&lt;li&gt;Habit of verifying assumptions before building&lt;/li&gt;
&lt;li&gt;Understanding that complexity compounds — keep it simple at the component level&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All four apply directly to building production AI systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Translation
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Engineering Concept&lt;/th&gt;
&lt;th&gt;AI Equivalent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Load path&lt;/td&gt;
&lt;td&gt;Data flow between agents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failure mode&lt;/td&gt;
&lt;td&gt;Hallucination or routing error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code reference (AISC, ACI)&lt;/td&gt;
&lt;td&gt;LLM system prompt constraints&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shop drawings&lt;/td&gt;
&lt;td&gt;Agent tool schemas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RFI&lt;/td&gt;
&lt;td&gt;Human approval gate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Punch list&lt;/td&gt;
&lt;td&gt;Audit agent output&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The Path
&lt;/h2&gt;

&lt;p&gt;I did not leave engineering to build software. I layered software on top of engineering.&lt;/p&gt;

&lt;p&gt;The job funds the empire build. The empire builds the exit. The engineering knowledge makes both more defensible.&lt;/p&gt;

&lt;p&gt;You do not need to choose between technical depth and building businesses. The technical depth is the competitive advantage.&lt;/p&gt;

</description>
      <category>engineering</category>
      <category>ai</category>
      <category>career</category>
      <category>python</category>
    </item>
    <item>
      <title>Self-Hosted vs Managed: The Real Cost Breakdown After 6 Months</title>
      <dc:creator>Domonique Luchin</dc:creator>
      <pubDate>Wed, 06 May 2026 10:00:02 +0000</pubDate>
      <link>https://dev.to/domoniqueluchin/self-hosted-vs-managed-the-real-cost-breakdown-after-6-months-564c</link>
      <guid>https://dev.to/domoniqueluchin/self-hosted-vs-managed-the-real-cost-breakdown-after-6-months-564c</guid>
      <description>&lt;p&gt;I run six businesses on a single $24/month VPS.&lt;/p&gt;

&lt;p&gt;Here is what that actually costs compared to the managed alternative.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Stack vs the SaaS Equivalent
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th&gt;What I Use&lt;/th&gt;
&lt;th&gt;Monthly Cost&lt;/th&gt;
&lt;th&gt;SaaS Equivalent&lt;/th&gt;
&lt;th&gt;SaaS Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Phone system&lt;/td&gt;
&lt;td&gt;Asterisk + VoIP.ms&lt;/td&gt;
&lt;td&gt;$29.70&lt;/td&gt;
&lt;td&gt;RingCentral&lt;/td&gt;
&lt;td&gt;$99/user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;Supabase (free tier)&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;PlanetScale&lt;/td&gt;
&lt;td&gt;$39&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI agents&lt;/td&gt;
&lt;td&gt;VAPI + Claude API&lt;/td&gt;
&lt;td&gt;Usage&lt;/td&gt;
&lt;td&gt;Bland AI&lt;/td&gt;
&lt;td&gt;$150+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content pipeline&lt;/td&gt;
&lt;td&gt;Custom pg_cron + Edge Functions&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;Buffer Pro&lt;/td&gt;
&lt;td&gt;$100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lead scraping&lt;/td&gt;
&lt;td&gt;CrawlOS on VPS&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;BatchLeads&lt;/td&gt;
&lt;td&gt;$299&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Credit dispute automation&lt;/td&gt;
&lt;td&gt;Custom FCRA engine&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;DisputeBee&lt;/td&gt;
&lt;td&gt;$69&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server&lt;/td&gt;
&lt;td&gt;Vultr VPS (4 vCPU / 8 GB)&lt;/td&gt;
&lt;td&gt;$24&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;My total: ~$75/month&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SaaS equivalent: ~$800/month&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hidden Costs of Self-Hosting
&lt;/h2&gt;

&lt;p&gt;Being honest here. Self-hosting is not free.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Time to build: 200+ hours over 6 months&lt;/li&gt;
&lt;li&gt;Time to maintain: 3 to 5 hours per week&lt;/li&gt;
&lt;li&gt;Debugging cost: High at first, drops significantly after 60 days&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Break-Even
&lt;/h2&gt;

&lt;p&gt;At $725/month savings, the build time pays off in the first month at a $50/hour rate.&lt;/p&gt;

&lt;p&gt;After month one, it is pure margin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who This Makes Sense For
&lt;/h2&gt;

&lt;p&gt;If you can build it, own it. If you cannot build it and cannot learn to, pay for it.&lt;/p&gt;

&lt;p&gt;The math only works if the self-hosted version actually runs reliably. A broken self-hosted stack is worse than SaaS — you pay in time and still do not get the output.&lt;/p&gt;

&lt;p&gt;Build it right the first time. Then own it forever.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>selfhosted</category>
      <category>cloud</category>
      <category>entrepreneurship</category>
    </item>
    <item>
      <title>County Record Scraping: How CrawlOS Finds 125 Leads Per Night Across 14 Texas Counties</title>
      <dc:creator>Domonique Luchin</dc:creator>
      <pubDate>Tue, 05 May 2026 10:00:02 +0000</pubDate>
      <link>https://dev.to/domoniqueluchin/county-record-scraping-how-crawlos-finds-125-leads-per-night-across-14-texas-counties-naf</link>
      <guid>https://dev.to/domoniqueluchin/county-record-scraping-how-crawlos-finds-125-leads-per-night-across-14-texas-counties-naf</guid>
      <description>&lt;p&gt;Every night while I sleep, a Playwright scraper hits 14 Texas county deed record portals and logs 125 qualified leads to my CRM.&lt;/p&gt;

&lt;p&gt;Here is how CrawlOS works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem With Paid Data
&lt;/h2&gt;

&lt;p&gt;Real estate lead services charge $200 to $500 per month for the same public data that county appraisal districts publish for free.&lt;/p&gt;

&lt;p&gt;I built a scraper instead.&lt;/p&gt;

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

&lt;p&gt;CrawlOS is a Python + Playwright system running on lb-telecom-01 (my Vultr VPS in Dallas).&lt;/p&gt;

&lt;p&gt;It hits the public-facing portal for each county, extracts recent deed filings, filters by acquisition criteria, and inserts qualifying records into a Supabase table.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 14 Counties
&lt;/h2&gt;

&lt;p&gt;Harris, Fort Bend, Montgomery, Brazoria, Galveston, Chambers, Liberty, Waller, Austin, Colorado, Wharton, Matagorda, Jackson, and Victoria.&lt;/p&gt;

&lt;p&gt;All Gulf Coast and surrounding markets — the territory I know from six years of O&amp;amp;G structural work in the region.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Filter Logic
&lt;/h2&gt;

&lt;p&gt;Not every deed transfer is a lead. The scraper applies filters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transfer type: warranty deed, not quitclaim&lt;/li&gt;
&lt;li&gt;Price range: within acquisition parameters&lt;/li&gt;
&lt;li&gt;Property type: residential or light commercial&lt;/li&gt;
&lt;li&gt;Recency: filed within the last 30 days&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Anything passing all four filters gets inserted. Everything else is discarded.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Output
&lt;/h2&gt;

&lt;p&gt;125 leads per night on average. Each record includes parcel ID, grantor/grantee, transfer amount, address, and filing date.&lt;/p&gt;

&lt;p&gt;Load Bearing Capital works the list. Petroleum Noir runs a parallel filter on mineral rights transfers from the same pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;The data is public. It has always been public. The advantage is not access — it is automation.&lt;/p&gt;

&lt;p&gt;Anyone can pull Harris County deed records manually. Almost nobody does it every night across 14 counties without lifting a finger.&lt;/p&gt;

</description>
      <category>python</category>
      <category>scraping</category>
      <category>automation</category>
      <category>realestate</category>
    </item>
    <item>
      <title>Building Load Bearing Empire From Your Phone During Off Hours</title>
      <dc:creator>Domonique Luchin</dc:creator>
      <pubDate>Tue, 05 May 2026 01:30:29 +0000</pubDate>
      <link>https://dev.to/domoniqueluchin/building-load-bearing-empire-from-your-phone-during-off-hours-2fpc</link>
      <guid>https://dev.to/domoniqueluchin/building-load-bearing-empire-from-your-phone-during-off-hours-2fpc</guid>
      <description>&lt;p&gt;I do not have a home office setup. I work from my phone.&lt;/p&gt;

&lt;p&gt;Here is how I run six AI businesses after a full day of structural engineering.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Constraint
&lt;/h2&gt;

&lt;p&gt;I work 8 to 10 hours a day as a Lead Structural Engineer. When I get home I am tired. I do not have the energy to sit at a desk for three more hours.&lt;/p&gt;

&lt;p&gt;So I do not. I work from my couch, from my bed, during lunch breaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes It Possible
&lt;/h2&gt;

&lt;p&gt;Automation handles the volume. I handle the decisions.&lt;/p&gt;

&lt;p&gt;My systems run nightly jobs, generate leads, draft content, and answer phones without me. I review outputs, approve or reject, and move on.&lt;/p&gt;

&lt;p&gt;The decision layer is the only part that requires my brain. I have compressed that to 30 to 60 minutes per night.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tools
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Claude on mobile — architecture, writing, strategy, debugging&lt;/li&gt;
&lt;li&gt;Supabase dashboard — checking table data, reviewing queue status&lt;/li&gt;
&lt;li&gt;Termius — SSH into lb-telecom-01 when something needs a direct fix&lt;/li&gt;
&lt;li&gt;Vercel — deployment monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Workflow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Check JARVIS memory table for overnight activity&lt;/li&gt;
&lt;li&gt;Review content queue — approve or reject pending posts&lt;/li&gt;
&lt;li&gt;Check lead pipeline — flag anything needing manual follow-up&lt;/li&gt;
&lt;li&gt;One new build decision per night — what gets worked on next&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is it. Thirty minutes. Everything else runs itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Skill
&lt;/h2&gt;

&lt;p&gt;The skill is not technical. It is designing systems that do not require you.&lt;/p&gt;

&lt;p&gt;Every time I build something, I ask: what happens when I do not touch this for two weeks? If the answer is "it breaks," I have not built a system. I have built a job.&lt;/p&gt;

&lt;p&gt;Build systems, not jobs.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>entrepreneurship</category>
      <category>ai</category>
      <category>mobile</category>
    </item>
  </channel>
</rss>
