<?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: Mohammed Arshad Ansari</title>
    <description>The latest articles on DEV Community by Mohammed Arshad Ansari (@mohammed_arshadansari_f2).</description>
    <link>https://dev.to/mohammed_arshadansari_f2</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3609964%2Fd0a642d8-0372-4976-99f8-0575aa92a93c.png</url>
      <title>DEV Community: Mohammed Arshad Ansari</title>
      <link>https://dev.to/mohammed_arshadansari_f2</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mohammed_arshadansari_f2"/>
    <language>en</language>
    <item>
      <title>Why is no one signing up for your SaaS? Four leaks, in order</title>
      <dc:creator>Mohammed Arshad Ansari</dc:creator>
      <pubDate>Wed, 12 Aug 2026 08:00:00 +0000</pubDate>
      <link>https://dev.to/mohammed_arshadansari_f2/why-is-no-one-signing-up-for-your-saas-four-leaks-in-order-4c6d</link>
      <guid>https://dev.to/mohammed_arshadansari_f2/why-is-no-one-signing-up-for-your-saas-four-leaks-in-order-4c6d</guid>
      <description>&lt;p&gt;You shipped. The product works. The Show HN post got a few upvotes — and the signups chart is a flat line.&lt;/p&gt;

&lt;p&gt;The reflex is to conclude the product is not good enough yet and build more of it. That reflex is almost always wrong, and expensive, because building is the one activity that feels like progress while changing nothing. No-signups has four usual causes, and they must be checked &lt;strong&gt;in order&lt;/strong&gt; — a fix downstream is invisible while an upstream leak is open.&lt;/p&gt;

&lt;h2&gt;
  
  
  Leak 1 — Nobody with the problem is arriving
&lt;/h2&gt;

&lt;p&gt;How many people who plausibly &lt;em&gt;have the problem your product solves&lt;/em&gt; saw your page last week? Not peers, not launch-day tourists — potential users. For most SaaS with zero signups the honest answer is "a handful", and no page converts a handful of anybody. If that is you, stop optimising the page and go where the problem is discussed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Leak 2 — It is built for "anyone"
&lt;/h2&gt;

&lt;p&gt;"A project management tool for teams" selects nobody. When a product is for everyone, no visitor feels it is for &lt;em&gt;them&lt;/em&gt;. A product for "developers" loses to a product for "freelance mobile developers who bill hourly" every time the second one shows up — even if yours is technically better. Narrowing is not shrinking your market; it is choosing the smallest market you can actually win.&lt;/p&gt;

&lt;h2&gt;
  
  
  Leak 3 — No answer to "why you specifically?"
&lt;/h2&gt;

&lt;p&gt;A visitor who has the problem and feels selected still asks one silent question: &lt;em&gt;why this one and not the incumbent I have already heard of?&lt;/em&gt; If your page describes you as a member of a category ("a modern CRM"), the visitor fills in the answer themselves — usually "no reason".&lt;/p&gt;

&lt;h2&gt;
  
  
  Leak 4 — The page talks about the product, not the visitor
&lt;/h2&gt;

&lt;p&gt;Features are evidence, not argument. The visitor is asking "what does my Tuesday look like after I sign up?" and the page is answering "we use vector search". First sentence: their problem. Second: the outcome. Features prove it lower down.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five-minute test
&lt;/h2&gt;

&lt;p&gt;Write one sentence: &lt;em&gt;"[Product] is for [the narrowest group you would bet on] who [specific situation], so they can [outcome they would pay for]."&lt;/em&gt; If you cannot fill it in without "anyone", "teams", or "businesses" — that is your leak, and no amount of traffic fixes it before the sentence does.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published as a &lt;a href="https://www.strangertosold.com/learn/saas-no-signups?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=fieldnotes" rel="noopener noreferrer"&gt;field note&lt;/a&gt; at strangertosold.com. The free &lt;a href="https://www.strangertosold.com/quiz?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=fieldnotes" rel="noopener noreferrer"&gt;3-minute Sales Bottleneck Finder&lt;/a&gt; — 18 questions, no email — names which of the nine stages of your sales system is actually blocking you.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>marketing</category>
      <category>saas</category>
      <category>startup</category>
    </item>
    <item>
      <title>"I can build, but nobody buys": the technical founder-2019s sales problem</title>
      <dc:creator>Mohammed Arshad Ansari</dc:creator>
      <pubDate>Sat, 08 Aug 2026 08:00:00 +0000</pubDate>
      <link>https://dev.to/mohammed_arshadansari_f2/i-can-build-but-nobody-buys-the-technical-founderu2019s-sales-problem-542d</link>
      <guid>https://dev.to/mohammed_arshadansari_f2/i-can-build-but-nobody-buys-the-technical-founderu2019s-sales-problem-542d</guid>
      <description>&lt;p&gt;&lt;strong&gt;"I can build, but nobody buys."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That sentence is the defining problem of the AI era for technical people. Building used to be the moat: if you could ship, you were rare. Now anyone can ship a working product in a weekend — which means the bottleneck has moved, for everyone, to the same place: strangers becoming customers. And most of us never trained for that at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  It is not a personality flaw
&lt;/h2&gt;

&lt;p&gt;Technical people tell themselves a specific story: &lt;em&gt;I am not a salesperson. Not pushy enough, smooth enough, extroverted enough.&lt;/em&gt; The story survives because it is flattering — it frames the gap as character rather than skill, and you cannot be blamed for your character. But watch what it protects: as long as selling is a personality you were not born with, you are excused from learning it, and free to do the thing that feels productive instead. Which is build more.&lt;/p&gt;

&lt;p&gt;The reframe that changes everything: the pushy salesperson is not the skilled version of selling — it is the &lt;em&gt;failed&lt;/em&gt; version. Pushing is what people do when they have skipped every earlier step and are trying to force a stranger through a wall. Skilled selling is closer to what you already do when you debug: observe, hypothesise, test, fix the actual fault.&lt;/p&gt;

&lt;h2&gt;
  
  
  "One more feature" is procrastination with a clean conscience
&lt;/h2&gt;

&lt;p&gt;When the product is not selling, building feels like the responsible response. It has visible output, it is within your control, and nobody rejects you while you do it. That last property is the tell. Adding a feature is the only response to "nobody buys" that involves zero contact with the people not buying — which is why it is the most popular and the least effective. The information you are missing is not in your codebase. It is in their heads, and there is no API for that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What selling actually is
&lt;/h2&gt;

&lt;p&gt;A stranger becomes a customer along a path with fixed stages: they have to want something (market), see why you specifically (position), find the trade obviously worth it (offer), understand you instantly (message), encounter you at all (attention), have a route from curious to committed (journey), and be listened to rather than pitched at (conversation). None of those requires charisma. Every one can be diagnosed.&lt;/p&gt;

&lt;p&gt;The trap: the stages fail silently and point the blame downstream. Weak positioning presents as "a marketing problem". A missing journey presents as "bad luck with conversions". So builders fix the visible symptom while the actual fault sits untouched two stages earlier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do this today
&lt;/h2&gt;

&lt;p&gt;Write down, as a falsifiable statement, why you believe people are not buying — the way you would write a bug hypothesis. If you cannot finish the sentence with something you have tested against real potential buyers, that is the diagnosis: you are debugging without logs. Your next unit of work is not code — it is five conversations.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published as a &lt;a href="https://www.strangertosold.com/learn/built-it-nobody-buys?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=fieldnotes" rel="noopener noreferrer"&gt;field note&lt;/a&gt; at strangertosold.com. If you want logs instead of theory: the free &lt;a href="https://www.strangertosold.com/quiz?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=fieldnotes" rel="noopener noreferrer"&gt;3-minute Sales Bottleneck Finder&lt;/a&gt; — 18 questions, no email — names the weakest of the nine stages in your sales system.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>saas</category>
      <category>software</category>
      <category>startup</category>
    </item>
    <item>
      <title>I systematised sales like an engineer: 9 stages, one path, and a free 3-minute diagnostic</title>
      <dc:creator>Mohammed Arshad Ansari</dc:creator>
      <pubDate>Mon, 03 Aug 2026 05:15:00 +0000</pubDate>
      <link>https://dev.to/mohammed_arshadansari_f2/i-systematised-sales-like-an-engineer-9-stages-one-path-and-a-free-3-minute-diagnostic-2e1o</link>
      <guid>https://dev.to/mohammed_arshadansari_f2/i-systematised-sales-like-an-engineer-9-stages-one-path-and-a-free-3-minute-diagnostic-2e1o</guid>
      <description>&lt;p&gt;I learned sales the way an engineer would — by refusing to believe it can't be systematised.&lt;/p&gt;

&lt;p&gt;The knowledge existed: Godin on permission, Dunford on positioning, Hormozi on offers, Miller on message, Brunson on funnels, Voss on negotiation. Each held one piece. Nobody held the sequence.&lt;/p&gt;

&lt;p&gt;So I arranged it: nine stages, one path — the order a buyer actually travels.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mindset → Market → Position → Offer → Message → Attention → Journey → Conversation → Scale.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The engineering insight that made it click: a sales system is a pipeline, and a pipeline is only as strong as its weakest stage. Most advice fails for a boring reason — it's the right fix for the wrong stage. Upstream problems masquerade as downstream ones: weak positioning looks like a lead problem, a missing journey looks like a closing problem. So people buy ads to fix positioning and sales training to fix funnels, and nothing moves.&lt;/p&gt;

&lt;p&gt;The debugging rule applies to revenue too: diagnose before you patch.&lt;/p&gt;

&lt;p&gt;I built a free 3-minute test that does the diagnosis — 18 questions, no email, no login. It names the exact stage costing you customers and gives you one concrete action for it today.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://www.strangertosold.com/quiz?utm_source=devto&amp;amp;utm_campaign=launch" rel="noopener noreferrer"&gt;Take the Sales Bottleneck Finder&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Based on my book, From Stranger to Sold.)&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>The most dangerous API response is HTTP 200 with an empty body</title>
      <dc:creator>Mohammed Arshad Ansari</dc:creator>
      <pubDate>Sun, 02 Aug 2026 19:02:07 +0000</pubDate>
      <link>https://dev.to/mohammed_arshadansari_f2/the-most-dangerous-api-response-is-http-200-with-an-empty-body-3j8g</link>
      <guid>https://dev.to/mohammed_arshadansari_f2/the-most-dangerous-api-response-is-http-200-with-an-empty-body-3j8g</guid>
      <description>&lt;p&gt;Every pipeline eventually inherits a source that moves. Ours did: &lt;code&gt;dataservices.imf.org&lt;/code&gt;&lt;br&gt;
stopped resolving in DNS entirely — not a 500, not a timeout, the hostname itself was gone.&lt;br&gt;
The IMF had migrated its data platform and the old host was on its way out. No villain in&lt;br&gt;
this story; following a publisher to its new home is the consumer's job.&lt;/p&gt;

&lt;p&gt;The interesting part is the failure mode the migration exposed, which has nothing to do with&lt;br&gt;
the IMF and everything to do with how most of us write ingestion code.&lt;/p&gt;
&lt;h2&gt;
  
  
  The symptom
&lt;/h2&gt;

&lt;p&gt;Monthly CPI for a set of non-OECD countries went quietly stale, frozen at December 2025, while&lt;br&gt;
every other source kept ticking. Nothing screamed. The daily job ran green. Downstream scoring&lt;br&gt;
kept computing on last-known values, because CPI resolves through a fallback chain — if the&lt;br&gt;
freshest monthly print is unavailable, the model reaches for the next-best source rather than&lt;br&gt;
failing outright.&lt;/p&gt;

&lt;p&gt;That fallback is a feature: one dead source should not take scoring down for a whole tier of&lt;br&gt;
countries. It also has a cost. &lt;strong&gt;Graceful degradation and silent staleness are the same&lt;br&gt;
mechanism viewed from two angles.&lt;/strong&gt; A freshness dashboard is what keeps the second angle&lt;br&gt;
visible; without one, the design that saves you also hides the problem from you.&lt;/p&gt;
&lt;h2&gt;
  
  
  The migration itself
&lt;/h2&gt;

&lt;p&gt;The new home is &lt;code&gt;api.imf.org&lt;/code&gt;, on SDMX 2.1, and it still speaks the same StructureSpecific XML&lt;br&gt;
dialect — so the parser barely changed. All the pain was in the &lt;em&gt;keys&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPI now lives on the &lt;code&gt;IMF.STA,CPI&lt;/code&gt; dataflow, and the all-items series is keyed with COICOP
&lt;code&gt;_T&lt;/code&gt; (the SDMX "total" convention), not the &lt;code&gt;CP00&lt;/code&gt; the old platform used.&lt;/li&gt;
&lt;li&gt;Balance-of-payments moved to &lt;code&gt;IMF.STA,BOP&lt;/code&gt;, where the legacy indicator codes do &lt;strong&gt;not&lt;/strong&gt; port
one-to-one. No find-and-replace; each series had to be re-derived against the new structure
definition and re-tested.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tedious rather than clever — and easy to get subtly wrong, because of what a wrong key does.&lt;/p&gt;
&lt;h2&gt;
  
  
  The gotcha worth stealing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A bad key returns HTTP 200 with an empty &lt;code&gt;DataSet&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not a 404. Not a 400. A cheerful &lt;code&gt;200 OK&lt;/code&gt;, a well-formed SDMX document, zero observations&lt;br&gt;
inside it. If your ingestion trusts the status line:&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;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;          &lt;span class="c1"&gt;# &amp;amp;lt;- the bug
&lt;/span&gt;    &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&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="nf"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                &lt;span class="c1"&gt;# stores nothing, reports success
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…then a typo in a dimension code, or an un-migrated series key, sails through as success and&lt;br&gt;
writes nothing. Job green. Data frozen. You find out weeks later from a freshness alert, if&lt;br&gt;
you have one, or from a customer, if you do not.&lt;/p&gt;

&lt;p&gt;Our fix is a &lt;strong&gt;canary&lt;/strong&gt;: the request always carries a segment we know must return data — United&lt;br&gt;
States all-items CPI — and a parse that yields zero series therefore cannot mean "no data".&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;series&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_sdmx21_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xml_bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;series&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;IMFIFSUnavailableError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IMF.STA,CPI returned an empty DataSet incl. the USA canary — &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key schema change or platform fault&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;The USA rows are then filtered &lt;em&gt;out&lt;/em&gt; before storage — its index base differs from our other&lt;br&gt;
US source and double-sourcing would corrupt the derived series. The canary lives in the&lt;br&gt;
request, not in the warehouse.&lt;/p&gt;

&lt;p&gt;Three lines, and it is the difference between finding a structural break in one run versus one&lt;br&gt;
month. Having built it for CPI, it generalises to every SDMX-style source that can return a&lt;br&gt;
well-formed empty response: pick a segment that must exist, assert it came back populated, let&lt;br&gt;
a broken key contract throw. Note what it is &lt;em&gt;not&lt;/em&gt; for: a genuine upstream outage is a&lt;br&gt;
different event, and that one deliberately does &lt;strong&gt;not&lt;/strong&gt; raise — it materialises with an&lt;br&gt;
&lt;code&gt;imf_ifs_outage&lt;/code&gt; flag, because failing hard there would skip every downstream scoring asset in&lt;br&gt;
the same run.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same bug one layer up: a fetch that succeeds is not a document
&lt;/h2&gt;

&lt;p&gt;The identical mistake, in a different shape, was sitting in our central-bank statement&lt;br&gt;
scrapers. They fetched a listing page, followed a link, extracted text, and handed it to an&lt;br&gt;
LLM for a hawkish/dovish read. Every step returned 200. Every step "worked".&lt;/p&gt;

&lt;p&gt;What was actually being scored, once we read the stored text back:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a bank's login form, because its statement listing redirects to &lt;code&gt;/login&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;a press-release index whose top headline was Treasury-bill auction results&lt;/li&gt;
&lt;li&gt;a SharePoint "you may be trying to access this site from a secured browser" notice&lt;/li&gt;
&lt;li&gt;115 characters of "this page depends on JavaScript"&lt;/li&gt;
&lt;li&gt;a rates &lt;em&gt;statistics&lt;/em&gt; nav link, because the link pattern &lt;code&gt;interest.*rate&lt;/code&gt; matched it and it
sorted first in the DOM&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The parallel to the empty &lt;code&gt;DataSet&lt;/code&gt; is exact: nothing failed, so nothing raised, so a&lt;br&gt;
plausible-looking value went downstream. The fixes were the same shape as the canary —&lt;br&gt;
assert the &lt;em&gt;content&lt;/em&gt;, not the transport:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A length floor and a bot/JS-notice check, so a page that cannot be a statement is skipped
rather than scored.&lt;/li&gt;
&lt;li&gt;Feeds and APIs instead of HTML scraping where the bank publishes one (RSS/Atom, or the
PDF-minutes API where the web page is a JS shell).&lt;/li&gt;
&lt;li&gt;A source is only enabled once its extracted text has been read back and confirmed to be a
monetary policy decision — and a source that cannot pass is switched off &lt;em&gt;in config with
the reason recorded&lt;/em&gt;, not left nominally "covered". Ten of seventeen configured banks are
on; seven are off behind a login wall, a client-rendered listing, or commercial bot
management we decline to work around.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Two reusable lessons
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Assert data presence, not status codes.&lt;/strong&gt; &lt;code&gt;200 OK&lt;/code&gt; means the HTTP conversation
succeeded. It says nothing about whether the payload contains what you asked for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connection outages and silent-empty responses are different failure modes needing
different handling.&lt;/strong&gt; A DNS failure throws — retry with backoff and you will know. A
200-plus-empty never throws, so &lt;code&gt;try/except&lt;/code&gt; + retry leaves it completely uncovered. It
needs an affirmative presence check. Handle both, separately, on purpose.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The payoff, incidentally, was not just damage control: following the source to its new home&lt;br&gt;
took BOP coverage from roughly 70 to 148 countries and made reserves available quarterly. The&lt;br&gt;
endpoint you are reluctantly forced onto is often the one the publisher is actually investing&lt;br&gt;
in.&lt;/p&gt;

&lt;p&gt;Full write-up, with the migration detail:&lt;/p&gt;

&lt;p&gt;What the pipeline feeds — a daily 0–100 credibility score for 169 countries on 100% free&lt;br&gt;
public data, with per-indicator provenance:&lt;/p&gt;




</description>
      <category>api</category>
      <category>backend</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>I open-sourced AEGIS: a self-hosted, flow-first personal AI orchestration platform</title>
      <dc:creator>Mohammed Arshad Ansari</dc:creator>
      <pubDate>Mon, 13 Jul 2026 15:26:15 +0000</pubDate>
      <link>https://dev.to/mohammed_arshadansari_f2/i-open-sourced-aegis-a-self-hosted-flow-first-personal-ai-orchestration-platform-4c74</link>
      <guid>https://dev.to/mohammed_arshadansari_f2/i-open-sourced-aegis-a-self-hosted-flow-first-personal-ai-orchestration-platform-4c74</guid>
      <description>&lt;p&gt;For the past year I've run most of my day on a system I built for exactly one user: me. Last week I open-sourced it. It's called AEGIS, it's MIT-licensed, and this is the honest tour.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bet
&lt;/h2&gt;

&lt;p&gt;Every week there's a new agent framework that promises to do everything. AEGIS is a smaller, stranger bet: that software can learn the shape of one person's life well enough to interrupt &lt;em&gt;less&lt;/em&gt;. It watches the boring things — tasks, email, money, a&lt;br&gt;
  knowledge base, homelab alerts — and only reaches for me when a decision is genuinely mine to make. It's not a chatbot I log into; it's a fleet of scheduled and event-driven workflows that mostly run without me.&lt;/p&gt;

&lt;p&gt;## The shape&lt;/p&gt;

&lt;p&gt;Four named agents, each a permission boundary with a personality: Sebas (GTD), Raphael (research), Maou (money), Pandora's Actor (infrastructure). The spine is FastAPI + Postgres (with pgvector) + Temporal, on a small Docker Swarm at home. Models&lt;br&gt;
  resolve through a LiteLLM proxy — local-first, reaching for Claude or GPT only when a job needs the horsepower.&lt;/p&gt;

&lt;p&gt;A few design decisions did most of the work.&lt;/p&gt;

&lt;p&gt;## One primitive for every interruption&lt;/p&gt;

&lt;p&gt;The decision I'm proudest of is a table. Every time the system needs a human, it's the same shape: a row in a Postgres &lt;code&gt;interactions&lt;/code&gt; table, a card in my chat app, and a Temporal workflow that durably waits — for days if it has to — until I tap a&lt;br&gt;
  button.&lt;/p&gt;

&lt;p&gt;Approvals, choices, drafts to review, plain acknowledgements — one mechanism, five card kinds, one callback format. No per-feature approval tables; adding a new "ask the human" moment costs nothing. And because interrupting me is now a formal act with a&lt;br&gt;
  paper trail, flows get written to do more work before they ask. That one decision turned AEGIS from a notification machine into a queue of interruptions that have to earn their way in.&lt;/p&gt;

&lt;p&gt;## Durability instead of cron-and-hope&lt;/p&gt;

&lt;p&gt;A card a workflow waits on for three days is miserable to build with cron and a queue — you hand-roll a state machine and reconcile it after every deploy. Temporal's durable execution is exactly this: the workflow awaits a signal, and the wait survives&lt;br&gt;
  restarts, redeploys, and the occasional node reboot. Schedules reconcile from DB config, so changing a flow's cadence needs no redeploy.&lt;/p&gt;

&lt;p&gt;## Behavior is data, not code&lt;/p&gt;

&lt;p&gt;The change that made AEGIS forkable was deleting every line that said &lt;code&gt;if agent == "sebas"&lt;/code&gt;. Capabilities, tool grants, and routing now live in database metadata, edited from an admin panel. The code asks "who owns GTD?" and gets an answer; it never&lt;br&gt;
  names names. Rename the agents, re-scope them, or add your own — no Python.&lt;/p&gt;

&lt;p&gt;## Local-LLM-first, for real&lt;/p&gt;

&lt;p&gt;Everything routes through a LiteLLM proxy exposing three tiers — fast / balanced / smart. Each agent is assigned a &lt;em&gt;tier&lt;/em&gt;, never a model name, so swapping models is proxy config and the app code never changes. One reasoning-model gotcha is handled&lt;br&gt;
  explicitly: reasoning models bill hidden reasoning tokens against &lt;code&gt;max_tokens&lt;/code&gt; before any visible output, so a tight cap returns &lt;code&gt;finish_reason=length&lt;/code&gt; with empty content — the client detects that and raises a typed truncation error instead of handing&lt;br&gt;
  an empty string to &lt;code&gt;json.loads&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;## What it is not&lt;/p&gt;

&lt;p&gt;Not a SaaS — no hosted version, and it does nothing until you point it at your own accounts and models. Not another framework to build on; it's a complete, opinionated application you fork and configure for your own life. If that sounds like more setup&lt;br&gt;
  than you want, reading the code is a perfectly good outcome.&lt;/p&gt;

&lt;p&gt;## Take it apart&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code: &lt;a href="https://github.com/hikmahtech/aegis" rel="noopener noreferrer"&gt;https://github.com/hikmahtech/aegis&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The longer tour + design essays: &lt;a href="https://hikmahtechnologies.com/aegis" rel="noopener noreferrer"&gt;https://hikmahtechnologies.com/aegis&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'd genuinely like to know what breaks — and what you'd reach for on the "smart" tier these days. That's the slot I still escalate most often.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>selfhosted</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
