<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:social="http://maho.dev/ns/social" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>The Raccoon Bytes</title>
    <link>https://maho.dev/</link>
    <description>The Raccoon Bytes - Bite-piece of knowledge from a garbage code connosseur</description>
    <language>en</language>
    <lastBuildDate>Tue, 10 Mar 2026 01:24:39 +0000</lastBuildDate>
    <atom:link href="https://maho.dev/index.xml" rel="self" type="application/rss+xml"/>
        <item>
      <title>AI is Making Libraries Obsolete</title>
      <link>https://maho.dev/2026/03/ai-is-making-libraries-obsolete/</link>
      <pubDate>Tue, 10 Mar 2026 01:24:39 +0000</pubDate>
      <guid>https://maho.dev/2026/03/ai-is-making-libraries-obsolete/</guid>
      <description>AI agents are fundamentally changing the economics of general-purpose libraries. Why build on Bootstrap when an agent can generate exactly what you need? A collection of thoughts on where this is all heading.</description>
      <content:encoded><![CDATA[<h1 id="the-great-library-unbundling-how-ai-is-eating-the-software-stack">The Great Library Unbundling: How AI is Eating the Software Stack</h1>
<p>More than a year ago, I told people that ORMs like Entity Framework were going to feel like relics. That LLMs would just generate the SQL you need, making all that object-relational mapping overhead pointless. A few people told me I was crazy and doing things wrong (and to be honest I wasn't good enough at explaining why I thought this).</p>
<p>Well, time seems to have been on my side (Wwo is laughing now hahahaha!!).</p>
<p>Now, this is a hot take, so I will go forward and talk as a hot take. If I put my devil's advocate hat on, I would probably rage against whoever wrote this post. But because I LOVE MYSELF, I won't do it. I certainly see flaws in my arguments. So buckle up—this is not rage bait, it is just a hot take from a Mexican developer who spent the weekend arguing with AI agents instead of sleeping like a normal person.</p>
<h2 id="the-orm-prediction-or-how-i-accidentally-became-right-about-something">The ORM Prediction, or How I Accidentally Became Right About Something</h2>
<p>Here's the thing about ORMs: they exist because writing SQL is supposedly &quot;hard&quot; and &quot;error-prone.&quot; Meanwhile, I've been writing raw SQL since my first job at a tiny shop in Tepic where we didn't have the luxury of Entity Framework or Hibernate or whatever fancy abstraction layer the cool kids were using. We just wrote the queries, crossed ourselves, and hit execute. Catholic upbringing has some unexpected engineering applications.</p>
<p>Now, to be fair—we did end up building our own tailored ORM for the common cases. Because once you've written the same parameterized INSERT fifteen times, even the most stubborn raw-SQL purist starts thinking &quot;maybe I should abstract this.&quot; And ORMs do earn their keep in some areas: SQL injection prevention, enforcing parameterized queries, handling connection lifecycle stuff that nobody wants to think about. Security guardrails that your 2 AM brain will absolutely forget to implement on its own. I get it. ORMs aren't stupid—they solved real problems.</p>
<p>But here's the trap: as soon as you start getting into complex queries, you're no longer just maintaining your application logic—you're also maintaining the ORM and the complex queries. You end up fighting the abstraction to make it do what raw SQL does naturally, and now you have two problems instead of one. The thing that was supposed to simplify your life becomes another layer you have to debug, optimize, and keep in your head at the same time. And whoever has run a high-performance application—like an ecommerce site during Black Friday—knows exactly what I'm talking about. That query you spent weeks trying to optimize through the ORM, doing quirky reflection tricks and fighting with expression trees? You ended up raw-dogging the SQL anyway, because that was the only way to get the performance you needed. The ORM was never going to get you there. It was just standing in the way, politely.</p>
<p>Now Claude just writes you the exact SQL you need—parameterized, injection-safe, the whole deal. No mapping, no configuration, no surprises. Just the data access pattern you actually want. It's like having a DBA who never sleeps, never complains about schema changes, and doesn't passive-aggressively CC your manager when you write a bad join. The abstraction layer that was supposed to save us from SQL is now the thing standing between us and the AI that's better at SQL than most of us ever were.</p>
<h2 id="agentic-development-and-the-death-of-one-size-fits-none">Agentic Development and the Death of One-Size-Fits-None</h2>
<p>This isn't just about ORMs. I've been thinking about how agentic development fundamentally changes the whole premise of general-purpose libraries and building blocks.</p>
<p>Platform teams across the industry are going to start questioning their investments in broad, one-size-fits-all libraries. And honestly? Good. Because &quot;one-size-fits-all&quot; always meant &quot;fits nobody perfectly but everyone tolerably.&quot; Like those beach ponchos they sell at every tourist shop in Nayarit—technically covers you, technically functions, but you look ridiculous and you know it.</p>
<p>Take CSS frameworks. Bootstrap, Bulma, Fluent UI—they're great for getting started quickly. But you end up learning their specific class naming conventions, carrying tons of CSS you'll never use, fighting against the framework the moment you want something custom, and—my personal favorite—looking like every other Bootstrap site on the internet. Your site ends up with more unused CSS than empty beer bottles after a Sunday carne asada in Nayarit. And that's a lot of bottles, trust me.</p>
<p>With agentic development? You just tell the agent what you want your UI to look like. It generates exactly the CSS you need. No framework lock-in, no unused code, no learning curve. The agent doesn't need to know Bootstrap's grid system (although it probably was trained on such codebases)—it just writes &quot;vanilla&quot; CSS that does what you asked for. I know this because that's exactly what happened with this blog. The CSS you're looking at right now? Generated. By an agent. Who understood what I wanted better than I understood what I wanted (debatable).</p>
<h2 id="mcp-is-already-feeling-old">MCP is Already Feeling Old</h2>
<p>Speaking of things that are aging quickly: the Model Context Protocol. MCP. Remember when everyone was wrapping every CLI tool as an MCP server like it was the new hotness? That was, what, months ago? In AI time that's basically the Paleolithic era.</p>
<p>Here's the thing—CLI tools already come with perfect documentation in the form of man pages. They're basically documented APIs already. Your agent doesn't need a special protocol wrapper to use <code>gh pr create</code> or <code>az webapp deploy</code>. It just reads the docs, fumbles the first attempt (like any of us), and then figures it out. Combine Claude Code with existing CLI tools—GitHub CLI, Azure CLI, kubectl, whatever—and you've got everything MCP promised, but without the ceremony.</p>
<p>Microsoft's already doing something interesting with their <a href="https://github.com/dotnet/skills">dotnet/skills</a> repo. Skills that are just prompts guiding the agent through repeatable processes. No protocol, no server, no serialization format drama. Just a well-written prompt. Turns out the best API for an AI agent is just... words. Who knew. (Okay, a lot of people knew. But still.)</p>
<h2 id="hugo-drove-me-to-build-my-own-again-because-i-never-learn">Hugo Drove Me to Build My Own (Again, Because I Never Learn)</h2>
<p>I just finished redoing my entire publishing workflow. Hugo felt limiting and bloated—all these features I'm maintaining, constantly broken by dependency updates, GitHub workflows failing for mysterious reasons. Every time I pushed a new post it was a coin flip whether the build would succeed or I'd spend an hour debugging some Go template issue that made me question my life choices.</p>
<p>So I rewrote it in .NET. Dead simple. I don't expect anyone else to use it—exclusive distribution, zero units available—but damn, it felt liberating. The CSS and HTML of this blog changed completely, and I actually understand every piece of it now. No more cargo-culting Hugo partials I copied from a theme three years ago and was afraid to touch.</p>
<p>This is the pattern I keep seeing: when AI can generate exactly what you need, the appeal of heavyweight, general-purpose solutions just evaporates. Like fog burning off in the morning sun in Tepic. One moment it's there, thick and omnipresent, and then it's just... gone.</p>
<h2 id="the-accountability-problem">The Accountability Problem</h2>
<p>Here's something that's been bugging me. I saw a conversation on the fediverse about more and more tools being created with no clear ownership. Developers suspect some might be entirely AI-generated, with humans just shepherding them into existence like zookeepers who can't actually control the animals.</p>
<p>That sucks. Humans should be in charge and taking responsibility for the software they put into the world. There's a difference between using AI as a tool and letting AI be the architect while you nod along pretending you reviewed the blueprints.</p>
<p>When something breaks, when there's a security issue, when users need support—who's accountable? You can't file a bug report against GPT-4. And &quot;the AI did it&quot; is not an incident postmortem. Not yet, anyway. Give it a year (debatable).</p>
<h2 id="copilots-infinite-loop-of-self-criticism">Copilot's Infinite Loop of Self-Criticism</h2>
<p>Speaking of AI quirks: Copilot keeps finding issues in my PRs, I ask it to fix them, it fixes them, and then when I ask it to review again, it finds more issues. In the code it just wrote. In the code. It. Just. Wrote.</p>
<p>This is like watching someone argue with themselves in the mirror. Except the mirror is burning tokens, the person is burning my budget, and the argument never ends. The AI equivalent of &quot;works on my machine&quot; syndrome, except it doesn't even work on its own machine.</p>
<h2 id="blog-posts-are-better-than-repos-for-teaching-ai">Blog Posts Are Better Than Repos for Teaching AI</h2>
<p>Here's something that genuinely surprised me. A friend at Microsoft pointed his AI agent to my blog series to implement ActivityPub. Not to a GitHub repo with code samples. Not to the official W3C spec. To my blog posts. The ones I write at 1 AM like a gremlin-raccoon, full of rambling asides and half-baked metaphors.</p>
<p>Turns out prose explanations with context and reasoning are way more effective for agents than just dumping code at them. Blog posts tell the story of <em>why</em> decisions were made, not just what the final result looks like. The agent needs to understand intent, not just syntax. It needs the narrative—the &quot;I tried this and it broke spectacularly, so I did this other thing instead&quot; part that never makes it into a README.</p>
<p>So all those years of writing meandering blog posts about my projects instead of writing proper documentation? Turns out I was ahead of my time. Or just lazy. Probably both.</p>
<h2 id="where-this-is-all-heading">Where This Is All Heading</h2>
<p>I think we're heading toward a world where general-purpose libraries become luxury items—nice to have, but not essential. Where AI-generated, purpose-built solutions become the norm. Where documentation and prose become more important than code artifacts. And where human accountability becomes the thing that actually differentiates good software from the rest.</p>
<p>This doesn't mean libraries disappear overnight. But the incentives are shifting. Why build and maintain a framework used by millions when everyone can have their own custom solution?</p>
<p>The future might be less about sharing code and more about sharing knowledge, context, and decision-making frameworks. Less &quot;here's my npm package&quot; and more &quot;here's the blog post explaining why I built it this way so your agent can do something better.&quot;</p>
<h2 id="anyway">Anyway</h2>
<p>This post is a collection of half-formed thoughts and observations. I'm not claiming to have all the answers—hell, I'm not even sure these are the right questions. But something is shifting under our feet, and pretending it isn't doesn't make it stop.</p>
<p>Are we heading toward a more fragmented, AI-generated software landscape? Or am I just another old developer yelling at algorithmic clouds from a small corner of the internet?</p>
<p>Honestly, I don't know. But I'd rather be wrong and loud about it than quiet and surprised when the whole toolchain landscape looks unrecognizable in five years.</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>AI</category>
      <category>Software Development</category>
      <category>Libraries</category>
      <category>Agentic Development</category>
      <category>ORMs</category>
      <category>CSS Frameworks</category>
      <category>MCP</category>
      <category>Developer Tools</category>
      <category>Future of Coding</category>
      <category>Hot Takes</category>
      <category>LLM</category>
      <category>Copilot</category>
    </item>
    <item>
      <title>If There&apos;s a Nazi at Your Bar, It&apos;s a Nazi Bar</title>
      <link>https://maho.dev/2026/02/if-theres-a-nazi-at-your-bar-its-a-nazi-bar/</link>
      <pubDate>Thu, 12 Feb 2026 01:00:00 +0000</pubDate>
      <guid>https://maho.dev/2026/02/if-theres-a-nazi-at-your-bar-its-a-nazi-bar/</guid>
      <description>I found out Substack isn&apos;t just tolerating extremism—it&apos;s literally hosting Nazi newsletters. So I&apos;m out. Here&apos;s why you should care.</description>
      <content:encoded><![CDATA[<blockquote>
<p>&quot;If there are nine people sitting at a table and a Nazi sits down, and nobody gets up, there are ten Nazis at the table.&quot; – Unknown</p>
</blockquote>
<p>When people first started saying Substack had a Nazi problem, I did what most of us do. I rationalized it. I assumed they were using &quot;Nazi&quot; the way the internet uses it—loosely, carelessly, as a blunt rhetorical weapon. Maybe it was white supremacist adjacent content hiding behind edgy language. Maybe it was dubious newsletters with dogwhistles that linked to actual extremist groups but maintained plausible deniability.</p>
<p>What I did not expect was to find <em>actual, mask-off, unambiguous Nazi content</em>. Full-blown antisemitism. Newsletters celebrating and promoting National Socialism. Not buried in some dark corner—hosted openly, on a platform where I was also publishing my work.</p>
<p>If you don't believe me, go look at what was at <code>natsoctoday1.substack.com</code>. Or read the reporting. It's all there:</p>
<ul>
<li><a href="https://www.theatlantic.com/ideas/archive/2023/11/substack-extremism-nazi-white-supremacy-newsletters/676156/">The Atlantic: Substack Has a Nazi Problem</a></li>
<li><a href="https://www.theguardian.com/media/2026/feb/07/revealed-how-substack-makes-money-from-hosting-nazi-newsletters">The Guardian: How Substack Makes Money From Nazi Newsletters</a></li>
<li><a href="https://www.engadget.com/apps/substack-accidentally-sent-push-alerts-promoting-a-nazi-publication-191004115.html">Engadget: Substack Accidentally Sent Push Alerts Promoting a Nazi Publication</a></li>
</ul>
<p>That last one is almost darkly funny. <em>Accidentally</em> promoted a Nazi newsletter through push notifications. An algorithm so indifferent to the content it amplifies that it just... pushed Nazism to people's phones. Like a coupon for hate.</p>
<p>This reminds me of something I learned early in my career. When you build software, the defaults matter. The things a platform chooses to allow, the content it monetizes, the guardrails it refuses to install—those aren't neutral decisions. They're architectural choices. A platform that takes a cut from Nazi content isn't a free speech champion. It's a business partner.</p>
<p>I've thought about this the way I think about any system design problem. There's always a tradeoff. Substack made theirs: growth and &quot;openness&quot; over basic human decency. And look, I'm no stranger to messy tradeoffs in tech. But there's a line. And if your platform is <em>literally making revenue from people who celebrate genocide</em>, you've blown past that line at full speed.</p>
<p>So I'm out. I've backed up everything to <a href="https://maho.dev">my blog</a>, where it always should have lived anyway. You can find me on <a href="https://maho.dev/me">Hachyderm</a>.</p>
<p>The bar metaphor is overused, but it's overused because it's true. If a Nazi sits down at your bar and you serve them, you've made a choice. If you <em>profit</em> from serving them, you've made an even louder one. And I don't want to drink at that bar.</p>
<p>Peace.</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>Ethics</category>
      <category>Tech Culture</category>
      <category>Substack</category>
      <category>Free Speech</category>
      <category>Platform Responsibility</category>
      <category>Hate Speech</category>
      <category>Internet Culture</category>
    </item>
    <item>
      <title>FOSDEM 2026: The Kid Who Dreamed of Hackers Found Them in Brussels</title>
      <link>https://maho.dev/2026/02/fosdem-2026-the-kid-who-dreamed-of-hackers-found-them-in-brussels/</link>
      <pubDate>Tue, 10 Feb 2026 01:24:39 +0000</pubDate>
      <guid>https://maho.dev/2026/02/fosdem-2026-the-kid-who-dreamed-of-hackers-found-them-in-brussels/</guid>
      <description>A kid from a small Mexican town dreamed of finding real-life hackers. Two decades later, he flew his family to Brussels and spoke at one of the world&apos;s largest open-source conferences. This is that story.</description>
      <content:encoded><![CDATA[<blockquote>
<p>&quot;We reject: kings, presidents and voting. We believe in: rough consensus and running code.&quot; – David D. Clark</p>
</blockquote>
<h2 id="the-dream">The Dream</h2>
<p>When I was a young hacker—yeah, believe it or not—my dream was to find other hackers in real life and just hang out together. That's it. That was the whole dream.</p>
<p>It sounds modest now, but you have to understand the context. I come from a very small town in Mexico, the kind of place where internet was a luxury, Linux was a word nobody recognized, and &quot;Windows&quot; was mostly what you opened to let the heat out. The idea of attending a tech conference was absurd. Attending one <em>in English</em>? In <em>another country</em>? That was pure science fiction—like telling my block friends about <a href="https://maho.dev/2024/12/the-power-of-representation/">Dragon Ball Z spoilers I'd read online, except even less believable</a>.</p>
<p>But with time, and a painfully slow DSL connection, I found my people. I stumbled into the <a href="https://web.archive.org/web/20050205011525/http://gulnay.org/index.php?op=4">local Linux user</a> group—fewer than ten of us in a city of thousands—and we built something from nothing. A hackerspace. Community events. Workshops with maybe a dozen attendees if we were lucky. Eventually, I found my way to national conferences and even talked at a few of them. Each one felt like a small victory, a tiny crack in the wall between where I was and where I wanted to be.</p>
<p><img src="/img/ducktravel_2.jpg" alt="A duck seats in top of coffee" /></p>
<h2 id="the-shot">The Shot</h2>
<p>So when the opportunity to submit a talk to FOSDEM 2026 appeared, I just shot my shot.</p>
<p>I did it almost by instinct, without overthinking it. FOSDEM—the Free and Open Source Software Developers' European Meeting—is one of the largest open-source conferences in the world. Thousands of developers, hundreds of talks, legendary project booths. It had always been a place that existed on the other side of a dream for me. But here's the thing: I'm more financially stable now, I've traveled to Europe for both leisure and work, and I speak comfortable (but still heavily accented) English. <a href="https://maho.dev/2023/10/embracing-accents-celebrating-diversity-in-language-and-culture/">I've made peace with my accent</a>—it's part of the package, take it or leave it.</p>
<p>So, why not? The real surprise was that I hadn't applied before.</p>
<h2 id="the-logistics-of-madness">The Logistics of Madness</h2>
<p>When my proposed talk was accepted, my first reaction wasn't joy—it was panic. The kind of panic you feel when you push to main and <em>then</em> read the diff. The real problem was logistics.</p>
<p>I already had a trip to Mexico planned for personal reasons. Going to FOSDEM meant extending the family travel by a week, rerouting flights, and solving the kind of logistical puzzle that makes your brain hurt. Tepic, a small city in the mountains of western Mexico → Mexico City → London → Brussels. With a seven-year-old. And a month's worth of luggage packed for both the scorching Mexican beach and a freezing European winter—flip-flops sharing suitcase space with thermal jackets, sunscreen next to wool scarves. And sanity (debatable).</p>
<p>After my wife—bless her patience—said &quot;just go for it,&quot; and after numerous conversations with both AI and non-AI advisors about how to make it less stressful, we committed. At the end of January, I found myself at the tiny airport of Tepic, eating the most amazing torta de pierna, beginning an absurd journey to Belgium.</p>
<p><img src="/img/ducktravel_5.jpg" alt="A duck explores cold Brussels streets" /></p>
<p>We crossed through London, hopped on the Eurostar to Brussels, and somewhere between countries, we lost a pillow—a bear-shaped one my kid had shamelessly stolen from his grandma. Rest in peace, little bear pillow. You survived a Mexican grandmother's house only to perish somewhere in the English Channel.</p>
<h2 id="the-candy-store">The Candy Store</h2>
<p>And then, there I was. At FOSDEM. With my kid. In Brussels.</p>
<p>The place was electric. People from every imaginable background wandered through the halls of the Université libre de Bruxelles. I'll be honest—there's still a noticeable lack of diversity, especially in gender representation—but the energy was undeniable. It felt like a living, breathing monument to what open source can be.</p>
<p>Seeing the project booths was like being a kid in a candy store—except I literally had a kid with me in this candy store. Mozilla, Thunderbird, Let's Encrypt, SUSE, and of course Mastodon, to name a few. I couldn't help myself; I told my son that when I was young, one of my first dreams was to work for SUSE. He listened carefully, the way seven-year-olds do when they're filing away information for later use (probably to embarrass me at dinner).</p>
<p><img src="/img/suse_incoming.jpg" alt="SUSE booth at FOSDEM" /></p>
<p>Keeping a seven-year-old entertained at a developer conference is its own extreme sport. Thankfully, a friend I hadn't seen in over a decade was there—with his kid. He's a <em>no-gringo</em>, a Dutchman who happens to have worked at Innox in Mexico. Our kids hit it off, and suddenly the conference had a parallel track: unsupervised children's chaos edition.</p>
<h2 id="the-talk">The Talk</h2>
<p>When the time came for my talk, I walked in, set up, and delivered something far from perfect—but unmistakably mine. I stumbled on a couple of words, my accent was thick, and I'm sure I made at least one joke that only landed for me. But that's the style. That's always been the style.</p>
<p>Just before stepping up, Elena handed me the most fabulous FOSDEM sweater in existence. People noticed. People asked where to get one. But no—only I could have it. <em>Exclusive distribution, zero units available.</em> (Okay fine, I was just lucky, but let me have this moment.)</p>
<p><img src="/img/photofriends3.jpg" alt="Friends in Sweaters" /></p>
<p>If I have one regret, it's not spending more time in other talks. It's not that I didn't try—I did—but balancing a seven-year-old's attention span with a conference schedule is a negotiation no diplomacy course prepares you for. I caught fragments, glimpses, enough to know I was missing incredible stuff. But that's the thing about FOSDEM: it's not a one-time event. I'll be back. And next time, I want to do more than speak—I want to listen, linger, and actually have those hallway conversations that everyone says are the best part of any conference.</p>
<p><img src="/img/photofriends1.jpg" alt="Friends enjoying FOSDEM" /></p>
<h2 id="the-kid-and-the-dream">The Kid and the Dream</h2>
<p>Here's what got me, though. The part I didn't expect.</p>
<p>My kid watched me speak at FOSDEM. He didn't fully understand the content—he's seven, and ActivityPub isn't exactly bedtime story material—but he saw his dad on a stage, in front of a room full of people, in another continent, talking about something he built. When the Q&amp;A started, he wanted to raise his hand. He got shy, though, and didn't. Later, visibly upset about his missed opportunity, he told me what he wanted to ask: &quot;Do you play Minecraft?&quot; In front of an auditorium full of open-source developers discussing federation protocols, my kid's burning question was about Minecraft. I love this human being more than I can express.</p>
<p><img src="/img/talkingfosdem1.jpg" alt="Maho speaking at FOSDEM" /></p>
<p>He asked questions the entire trip back: &quot;What does SUSE do?&quot; &quot;Will you talk at another one?&quot; &quot;Can I have my own desk computer?&quot;</p>
<p>He saw the booths, the projects, the people. He kept posing for photos with each open-source mascot like a tiny celebrity on a press tour. His favorite was the PostgreSQL elephant, though we were genuinely concerned about its health. Based on the state of that costume, I think he might be right—PostgreSQL could use your donations, folks. That elephant has seen better days.</p>
<p><img src="/img/postgresql-elephant-fosdem.jpg" alt="The PostgreSQL elephant mascot at FOSDEM" /></p>
<p>And the trip back was no less insane than the trip there. Brussels → Iceland → Seattle. Because apparently, when you're already doing something absurd, you might as well add a layover near the Arctic Circle. We landed in Reykjavík with our beach-and-winter Frankenstein luggage, stepped outside into wind that felt personally offended by our existence, and my kid asked if the land was actually made of ice. Close enough, kid. Close enough.</p>
<p><img src="/img/iceland.jpg" alt="Reykjavik, Iceland landscape" /></p>
<p>A week later, during a conversation with his teacher, my son was asked about the most memorable thing from the trip. He didn't say the beach in Mexico, or the train through Europe, or the wind in Iceland, or even the lost bear pillow. He said the most memorable thing was seeing his dad talk at a university. That it made him proud (I'm not going to pretend I didn't need a moment after hearing that).</p>
<p>I thought about my own childhood. About the kid who couldn't find a single hacker in his town. About the dusty streets and half-built houses. About how representation works in mysterious ways—<a href="110-power-representation">how seeing someone like you doing something impossible makes it feel possible</a>. My son doesn't know what it's like to not see a path. For him, this is just what dad does. And maybe that's the whole point.</p>
<h2 id="full-circle">Full Circle</h2>
<p><img src="/img/mefederated.jpg" alt="Maho at FOSDEM" /></p>
<p>Twenty years ago, I was a teenager in a small Mexican town, writing code in paper notebooks and dreaming of a world I could barely imagine. Today, I stood in Brussels and spoke to a room full of open-source developers about a project I created.</p>
<p>The path from there to here wasn't straight. It was messy, full of detours, broken English, lost pillows, and more coffee than any doctor would recommend. But every step—every hackerspace meetup with eight people, every local conference talk, every late night wrestling with code—was a brick in the road that led to that stage.</p>
<p>And yeah, I get it, talking for half an hour at a conference with hundreds of talks may seem like a small feat. One slot among many. But it wasn't small to me. For the kid who couldn't find a single hacker in his hometown, standing in front of that room was enormous.</p>
<p>FOSDEM wasn't just a conference for me. It was proof that the kid from Tepic who dreamed of finding hackers in real life finally did. They were in Brussels all along, waiting for him to show up.</p>
<p>And he brought his kid.</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>FOSDEM</category>
      <category>Open Source</category>
      <category>Conferences</category>
      <category>Community</category>
      <category>Travel</category>
      <category>Personal Growth</category>
      <category>Europe</category>
      <category>Public Speaking</category>
    </item>
    <item>
      <title>A Guide to Implementing ActivityPub in a Static Site (or Any Website) - Part 9: Quote Posts</title>
      <link>https://maho.dev/2026/02/a-guide-to-implementing-activitypub-in-a-static-site-or-any-website-part-9-quote-posts/</link>
      <pubDate>Tue, 03 Feb 2026 01:24:39 +0000</pubDate>
      <guid>https://maho.dev/2026/02/a-guide-to-implementing-activitypub-in-a-static-site-or-any-website-part-9-quote-posts/</guid>
      <description>Find the index and earlier parts of this series here . Quote Posts for Static Sites: A Practical Guide to FEP-044f Implementation Transform your static blog int...</description>
      <content:encoded><![CDATA[<blockquote>
<p>Find the <a href="https://maho.dev/2024/02/a-guide-to-implement-activitypub-in-a-static-site-or-any-website/">index and earlier parts of this series here</a>.</p>
</blockquote>
<h1 id="quote-posts-for-static-sites-a-practical-guide-to-fep-044f-implementation">Quote Posts for Static Sites: A Practical Guide to FEP-044f Implementation</h1>
<blockquote>
<p>Transform your static blog into a consent-respecting quote-enabled node in the fediverse. This guide shows you how to implement quote post support that works with Mastodon, GoToSocial, and other ActivityPub servers while respecting author preferences.</p>
</blockquote>
<p><strong>In this guide:</strong> You'll learn to build quote-enabled blog posts that can be responsibly shared across the fediverse</p>
<h2 id="why-quote-posts-matter-and-why-theyre-controversial">Why Quote Posts Matter (And Why They're Controversial)</h2>
<h3 id="the-user-experience-problem">The User Experience Problem</h3>
<p>Picture this: Someone finds your blog post fascinating and wants to share it with their followers, but they also want to add their own perspective or why is important. Without quote posts, they have two unsatisfying options:</p>
<ol>
<li><strong>Simple share</strong>: Just boost with no commentary (or reply)</li>
<li><strong>Link sharing</strong>: Add a link to the blog post in their note</li>
</ol>
<p>Neither option creates the rich, attributed conversations that make social media engaging.</p>
<h3 id="the-solution-consent-first-quote-implementation">The Solution: Consent-First Quote Implementation</h3>
<p>We're implementing <a href="https://codeberg.org/fediverse/fep/src/branch/main/fep/044f/fep-044f.md">FEP-044f: Consent-respecting quote posts</a> in our federated blog.</p>
<p><strong>What this means for your readers:</strong></p>
<ul>
<li>They can quote your posts with confidence that you've opted in</li>
<li>Their quotes include proper attribution and linking</li>
</ul>
<p><strong>What this means for you:</strong></p>
<ul>
<li>Automatic handling of quote requests</li>
<li>Future-ready for advanced moderation features (like in the fuuutuuure)</li>
</ul>
<h2 id="implementation-overview">Implementation Overview</h2>
<p>We are going to:</p>
<ol>
<li>Modify the Notes JSON to assert that the notes are quotable.</li>
<li>Modify our Index function (the only dynamic POST endpoint) to handle quote requests and send the appropriate approval back (blanket approval).</li>
</ol>
<h3 id="modifying-the-notes-enhanced-activitypub-context">1. Modifying the Notes: Enhanced ActivityPub Context</h3>
<p><strong>What We Changed:</strong>
Extended the <code>@context</code> from a simple string to a rich object array supporting the GoToSocial namespace.</p>
<p><strong>Before:</strong></p>
<pre><code class="language-json">&quot;@context&quot;: &quot;https://www.w3.org/ns/activitystreams&quot;
</code></pre>
<p><strong>After:</strong></p>
<pre><code class="language-json">&quot;@context&quot;: [
  &quot;https://www.w3.org/ns/activitystreams&quot;,
  {
    &quot;gts&quot;: &quot;https://gotosocial.org/ns#&quot;,
    &quot;interactionPolicy&quot;: {&quot;@id&quot;: &quot;gts:interactionPolicy&quot;, &quot;@type&quot;: &quot;@id&quot;},
    &quot;canQuote&quot;: {&quot;@id&quot;: &quot;gts:canQuote&quot;, &quot;@type&quot;: &quot;@id&quot;},
    &quot;automaticApproval&quot;: {&quot;@id&quot;: &quot;gts:automaticApproval&quot;, &quot;@type&quot;: &quot;@id&quot;}
  }
]
</code></pre>
<p>We are also adding this section at the end of the Note:</p>
<pre><code class="language-json">&quot;interactionPolicy&quot;: {
  &quot;canQuote&quot;: {
    &quot;automaticApproval&quot;: &quot;https://www.w3.org/ns/activitystreams#Public&quot;
  }
}
</code></pre>
<p>If you want to be specific about who can quote your post, this is where you do it, read more in <a href="https://codeberg.org/fediverse/fep/src/branch/main/fep/044f/fep-044f.md">here</a>.</p>
<p>You can see an example of the implementation in <a href="https://github.com/mahomedalid/almost-static-activitypub/blob/main/src/Rss2Outbox/RssUtils.cs">RssUtils.cs</a> - in the <code>GetNote</code> method.</p>
<h3 id="quote-request-processing">2: Quote Request Processing</h3>
<p>Now we need to add the quote request handling system that processes incoming quote requests and automatically approves them based on our interaction policy.</p>
<p><strong>New Components:</strong></p>
<ul>
<li><strong>QuoteRequestService</strong>: Processes incoming quote requests from the fediverse</li>
<li><strong>Auto-Approval Logic</strong>: Automatically approves public quote requests as defined in our interaction policy</li>
<li><strong>Quote Authorization</strong>: Issues authorization tokens (stamps) for approved quotes</li>
</ul>
<p><strong>The Quote Request Flow:</strong></p>
<p>{{&lt; mermaid &gt;}}
sequenceDiagram
participant Requester as Fediverse User
participant Inbox as Our Inbox
participant QRS as QuoteRequestService<br />
participant Target as Target Instance</p>
<pre><code>Requester-&gt;&gt;Inbox: QuoteRequest for our post
Inbox-&gt;&gt;QRS: Process quote request
QRS-&gt;&gt;QRS: Check interaction policy
QRS-&gt;&gt;QRS: Generate authorization stamp
QRS-&gt;&gt;Target: Send Accept + Authorization
Target-&gt;&gt;Requester: Quote approved
</code></pre>
<p>{{&lt; /mermaid &gt;}}</p>
<p>Checkout the implementation in the <a href="https://github.com/mahomedalid/almost-static-activitypub/blob/main/src/ActivityPubFunctions/QuoteRequestService.cs">QuoteRequestService.cs</a>.</p>
<h3 id="the-missing-piece-quote-authorization-stamps">3: The Missing Piece - Quote Authorization Stamps</h3>
<p>After implementing steps 1 and 2, I tried to test quotes and it seems to be working. However, although the post looked like quotes in my home instance (e.g. hachyderm.io) the same post in other instances (e.g. theforkiverse.com) was still showing as pending. This is because we're missing the <strong>QuoteAuthorization</strong> verification mechanism described in <a href="https://codeberg.org/fediverse/fep/src/branch/main/fep/044f/fep-044f.md#verifying-third-party-quote-posts">FEP-044f</a>.</p>
<p><strong>The Solution:</strong></p>
<p>When we approve a quote request, we need to generate a static QuoteAuthorization file at the stamp URL that third-party instances can fetch to verify the quote is legitimate. The home instance does not need this because it relies on the original Accept handshake, but it will send an <code>Update</code> activity to other instances with the stamp URL. It is in that URL where we need to store our quote authorization.</p>
<p><strong>Implementation:</strong></p>
<p>This is very similar to the <a href="https://maho.dev/2025/01/a-guide-to-implementing-activitypub-in-a-static-site-or-any-website-part-8/">replies generation explained in part 8</a>, where each time we receive a reply we update a collection of the note.</p>
<p>You can take a look at my dontnet implementation in <a href="https://github.com/mahomedalid/almost-static-activitypub/blob/main/src/ActivityPubDotNet.Core/Storage/StampsGenerator.cs">StampsGenerator.cs</a>.</p>
<p>The trick here, and something to pay attention to, is the <code>interactingObject</code>, <code>interactionTarget</code>, and <code>attributedTo</code> fields. This is one example:</p>
<pre><code class="language-json">{
  &quot;@context&quot;: [
    &quot;https://www.w3.org/ns/activitystreams&quot;,
    {
      &quot;QuoteAuthorization&quot;: &quot;https://w3id.org/fep/044f#QuoteAuthorization&quot;,
      &quot;gts&quot;: &quot;https://gotosocial.org/ns#&quot;,
      &quot;interactingObject&quot;: {
        &quot;@id&quot;: &quot;gts:interactingObject&quot;,
        &quot;@type&quot;: &quot;@id&quot;
      },
      &quot;interactionTarget&quot;: {
        &quot;@id&quot;: &quot;gts:interactionTarget&quot;,
        &quot;@type&quot;: &quot;@id&quot;
      }
    }
  ],
  &quot;type&quot;: &quot;QuoteAuthorization&quot;,
  &quot;id&quot;: &quot;https://maho.dev/socialweb/quotes/e7f5a28d-26d8-4c19-8bae-6d9bbb040b5e&quot;,
  &quot;attributedTo&quot;: &quot;https://maho.dev/@blog&quot;,
  &quot;interactingObject&quot;: &quot;https://theforkiverse.com/ap/users/115868408494460202/statuses/116014861405475931&quot;,
  &quot;interactionTarget&quot;: &quot;https://maho.dev/socialweb/notes/6078d5c5d08f3a17aca4642bba3d61f9&quot;
}
</code></pre>
<p>It took me a few times to actually get it right. <code>attributedTo</code> and <code>interactionTarget</code> refer to the original quoted post, while <code>interactingObject</code> is the note doing the quoting (from the remote server).</p>
<p>I decided to go with this file structure, but you can do whatever you want—also not necessarily unique GUIDs, but maybe hashes instead:</p>
<p><strong>File Structure:</strong></p>
<pre><code>your-site.com/socialweb/
├── quotes/
│   └── {guid}/          # QuoteAuthorization stamps files
└── notes/
    └── {post-id}/       # Your original posts
</code></pre>
<h3 id="a-clever-alternative">A clever alternative</h3>
<p>Claire from a Mastodon dev chat suggested a clever approach in case you use dynamic endpoints: instead of storing the actual authorization, you craft it at runtime. The cleverness is that you can encode <code>interactingObject</code> and <code>interactionTarget</code> as base64 and include them as part of the URL, e.g.:</p>
<pre><code>https://maho.dev/socialweb/quotes/{base64-encoded-interacting-object}/{base64-encoded-interaction-target}
</code></pre>
<p><strong>Example:</strong></p>
<ul>
<li><code>interactingObject</code>: <code>https://theforkiverse.com/ap/users/115868408494460202/statuses/116014861405475931</code></li>
<li><code>interactionTarget</code>: <code>https://maho.dev/socialweb/notes/6078d5c5d08f3a17aca4642bba3d61f9</code></li>
</ul>
<p>When base64-encoded and used in the URL:</p>
<pre><code>https://maho.dev/socialweb/quotes/aHR0cHM6Ly90aGVmb3JraXZlcnNlLmNvbS9hcC91c2Vycy8xMTU4Njg0MDg0OTQ0NjAyMDIvc3RhdHVzZXMvMTE2MDE0ODYxNDA1NDc1OTMx/aHR0cHM6Ly9tYWhvLmRldi9zb2NpYWx3ZWIvbm90ZXMvNjA3OGQ1YzVkMDhmM2ExN2FjYTQ2NDJiYmEzZDYxZjk%3D
</code></pre>
<p>This approach eliminates the need for static file storage while maintaining the same verification capabilities. Your dynamic endpoint can decode the URLs and generate the QuoteAuthorization JSON on-demand. And just in case it's not obvious, this is the URL that you return as stamp ID in the QuoteAuthorization on step 2.</p>
<h2 id="key-takeaways">Key Takeaways</h2>
<blockquote>
<p>By implementing FEP-044f, we're not just adding quote functionality - we're building consent-respecting social interactions into the protocol level.</p>
</blockquote>
<p><strong>Why This Matters:</strong></p>
<p>This implementation shows how static sites can participate in modern social web standards while keeping their simplicity and performance benefits. Right now, we're automatically allowing all public quotes, but this foundation sets us up for more granular consent controls in the future - like requiring approval for specific users or implementing follower-only quoting.</p>
<p>The consent-respecting approach means our content can be shared thoughtfully across the fediverse, with the infrastructure already in place to handle more sophisticated permission systems as they evolve.</p>
<h2 id="next-steps-the-quote-visualization-challenge">Next Steps: The Quote Visualization Challenge</h2>
<p>Now that we've successfully implemented the backend infrastructure for consent-respecting quote posts, we face an equally important question: <strong>How should we display these quotes on our website?</strong></p>
<p>Treat quoted posts as special reply types? Quotes have different semantic meaning than replies - they're more like &quot;shared with commentary&quot; So maybe
create a separate &quot;Quoted By&quot; section similar to how we handle likes and shares?</p>
<p>Any ideas?</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>Fediverse</category>
      <category>ActivityPub</category>
      <category>Static Sites</category>
      <category>Hugo</category>
      <category>Azure</category>
      <category>Mastodon</category>
      <category>Web Development</category>
      <category>Social Web</category>
      <category>WebFinger</category>
      <category>HTTP</category>
      <category>Quote Posts</category>
      <category>FEP-044f</category>
    </item>
    <item>
      <title>The Anachronistic Internet: When Cat Videos Actually Mattered</title>
      <link>https://maho.dev/2026/01/the-anachronistic-internet-when-cat-videos-actually-mattered/</link>
      <pubDate>Sat, 31 Jan 2026 01:24:39 +0000</pubDate>
      <guid>https://maho.dev/2026/01/the-anachronistic-internet-when-cat-videos-actually-mattered/</guid>
      <description>A late-night, sleep-deprived rant about how the internet used to work, triggered by watching 2 Broke Girls and realizing how anachronistic everything feels now.</description>
      <content:encoded><![CDATA[<h1 id="the-anachronistic-internet-when-cat-videos-actually-mattered">The Anachronistic Internet: When Cat Videos Actually Mattered</h1>
<p>So here I am, 3 AM, can't sleep, supposed to give a talk at FOSDEM in a few hours, and I'm pretty sure I'm getting sick after three weeks of travel. What does any rational human do in this situation? Watch 2 Broke Girls, obviously.</p>
<p>And holy shit, does that show feel anachronistic now.</p>
<h2 id="hipsters-dont-exist-anymore-and-other-revelations">Hipsters Don't Exist Anymore (And Other Revelations)</h2>
<p>First off, the hipster jokes. Nobody makes hipster jokes anymore because hipsters either don't exist or became less culturally relevant than emos. Remember emos? Yeah, that's how dead hipsters are.</p>
<p>But it wasn't just the cultural references that felt ancient. It was the internet itself.</p>
<p>There's this scene where Caroline (the blonde one) takes Max's laptop to do some business stuff and stumbles upon her browser history. Not search history—browser history. And what's in there? Cat videos. A cat ringing a doorbell. A kitten doing funny stuff. She rattles off 5 or 6 different videos.</p>
<p>And I'm sitting there, exhausted and probably feverish, thinking: &quot;Holy crap, that's how we used to consume content.&quot;</p>
<h2 id="when-million-views-actually-meant-something">When Million Views Actually Meant Something</h2>
<p>Remember when a video with a million views was a big deal? Not just numerically, but culturally? It meant millions of people actively sought that thing out. Someone told them about it—word of mouth, in real life—and they went home, opened their browser, fired up YouTube or Google, and searched for &quot;cat ringing doorbell&quot; or whatever.</p>
<p>They made a conscious choice to watch it.</p>
<p>That's so radically different from today it might as well be from a different species of internet.</p>
<p>Today, a million views means an algorithm shoved something in front of a million eyeballs. Half those people probably didn't even want to see it. They were just scrolling, trapped in the engagement machine, and the algorithm decided their attention belonged to that video for the next 30 seconds.</p>
<h2 id="the-browser-history-archaeological-dig">The Browser History Archaeological Dig</h2>
<p>Let's talk about browser history for a second. When was the last time you checked yours? I mean really looked at it?</p>
<p>Back in the day? Browser history was like an archaeological dig of your curiosity. It told the story of how you discovered things, how you followed rabbit holes from one interesting thing to another. You'd see the path from &quot;funny cat videos&quot; to &quot;how do cats see color&quot; to &quot;are cats colorblind&quot; to &quot;evolution of feline vision&quot; to &quot;why are my eyes dry&quot; to &quot;computer screen blue light&quot; to &quot;buying blue light glasses&quot;</p>
<p>That was the internet. A web of curiosity, not a feed of algorithmic manipulation.</p>
<h2 id="the-walled-garden-apocalypse">The Walled Garden Apocalypse</h2>
<p>Today, that same cat video discovery journey happens inside TikTok or Instagram Reels or YouTube Shorts. It's all contained within one app, one ecosystem, one company's idea of what you should see next.</p>
<p>The browser? It's basically just a container for apps now. Gmail, Slack, whatever productivity tool your company forces you to use. The actual web—the place where you could stumble upon weird personal blogs and random forums and people's actual thoughts—that's mostly dead.</p>
<p>We traded the open web for engagement algorithms and dopamine slot machines.</p>
<h2 id="when-sharing-was-intentional">When Sharing Was Intentional</h2>
<p>Here's another thing that hit me during my 2 Broke Girls insomnia spiral: sharing used to require effort.</p>
<p>If I wanted to show you a video, I had to copy the URL, paste it in an email or IM, and send it to you. You had to click it, wait for it to load, and make the conscious decision to watch it. There was friction, and that friction meant something.</p>
<p>Now? I can &quot;share&quot; something by double-tapping it, and it gets blasted to everyone who follows me, whether they want it or not. The algorithm decides who sees it and when. There's no intentionality, no curation, no thought.</p>
<p>We optimized the friction out of sharing and accidentally optimized the meaning out of it too.</p>
<h2 id="the-great-attention-heist">The Great Attention Heist</h2>
<p>This is what really gets me: somewhere along the way, we agreed to let algorithms decide what deserves our attention. We handed over one of the most precious resources we have—our focus—to systems designed to extract maximum engagement, not deliver maximum value.</p>
<p>In the old internet, your attention was yours. You decided to search for something. You decided to click on a link. You decided to bookmark something for later. You were the curator of your own experience.</p>
<p>Now? Your attention is a commodity being traded in real-time auctions you don't even know are happening.</p>
<h2 id="but-wait-it-gets-worse">But Wait, It Gets Worse</h2>
<p>The really messed up part is how normalized this has become. We act like this is just how the internet works, like it's some natural law. But it's not. It's a business model. A very specific, very recent business model that prioritizes engagement over everything else.</p>
<p>Quality? Doesn't matter as long as people keep scrolling.
Truth? Secondary to virality.
Your mental health? Not their problem.
Your time? Their most valuable asset.</p>
<p>We're not users anymore. We're the product. And we're being sold to advertisers who want to influence our behavior.</p>
<h2 id="the-fosdem-connection-because-why-not">The FOSDEM Connection (Because Why Not?)</h2>
<p>Speaking of influencing behavior—I'm supposed to talk about social web and community building at FOSDEM in a few hours. And maybe that's the connection here. The old internet was more like open source: decentralized, community-driven, built by people who cared about the craft, not the profit.</p>
<p>The new internet is more like proprietary software: controlled by a few big players, optimized for their benefit, not yours, and increasingly hostile to alternatives.</p>
<p>Maybe that's why I'm feeling so nostalgic for browser histories and intentional sharing and cat videos that people actually searched for. It wasn't just a different internet—it was a different philosophy about how technology should work.</p>
<h2 id="so-what-now">So What Now?</h2>
<p>I don't have a grand solution here. I'm literally writing this at 5 AM while probably getting sick and definitely procastinating.</p>
<p>But maybe awareness is the first step? Maybe we can start making more intentional choices about where we spend our attention? Maybe we can support platforms and tools that respect our agency instead of exploiting it?</p>
<p>Or maybe I'm just being an old man yelling at algorithmic clouds.</p>
<p>Either way, I should probably try to get some sleep before I have to explain why social web matters to a room full of people who already know why social web matters.</p>
<p>At least that's one thing that hasn't changed: programmers still love stating the obvious to each other at conferences.</p>
<hr />
<p><em>Update: The FOSDEM talk will be fine. Caffeine is a hell of a drug.</em></p>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>Internet Culture</category>
      <category>Social Media</category>
      <category>Technology</category>
      <category>Nostalgia</category>
      <category>Algorithms</category>
      <category>Digital Philosophy</category>
      <category>Old Web</category>
      <category>Content Discovery</category>
      <category>FOSDEM</category>
      <category>Random Thoughts</category>
    </item>
    <item>
      <title>The Forkiverse Experiment and Why Instance Choice Matters</title>
      <link>https://maho.dev/2026/01/the-forkiverse-experiment-and-why-instance-choice-matters/</link>
      <pubDate>Sun, 11 Jan 2026 17:00:00 +0000</pubDate>
      <guid>https://maho.dev/2026/01/the-forkiverse-experiment-and-why-instance-choice-matters/</guid>
      <description>A reflection on the Forkiverse experiment, why it felt different from day one, and how it changed my view on instance choice in the Fediverse.</description>
      <content:encoded><![CDATA[<p>I have not had so much fun and hope since joining the Fediverse as what I experienced this week with <a href="https://theforkiverse.com/home">the Forkiverse</a>.</p>
<p><a href="https://theforkiverse.com/home">The Forkiverse</a> is a deliberate experiment by the <a href="https://www.searchengine.show/"><em>Search Engine</em></a> and <a href="https://www.nytimes.com/column/hard-fork"><em>Hard Fork</em></a> podcasts. They created a Mastodon instance for themselves and their audience with an ambitious but familiar goal: try to fix what feels broken about the internet. I joined the bandwagon quickly. I already have accounts on other instances, but the experience here felt immediately different. The feed felt fresh, curious, and genuinely fun.</p>
<p>There is also something quietly genius about the idea itself. Podcasts and other large content creators creating their own social platforms, where their audience can interact not just with the hosts but with each other, feels like a natural evolution. This idea has been discussed for years, but rarely tested. It worked. Within 48 hours of releasing their episode, the Forkiverse had already attracted around 2.5K new accounts. That is not just excitement is some kind of proof of demand. Of course, their audience was mostly techie, and they have thousands of followers, which makes this a low digit % conversion, but still, it is interesting.</p>
<p>That does not mean everything was frictionless. Almost immediately, people began debating why it is called the Forkiverse, whether it is trying to compete with the Fediverse, or whether it is creating yet another Fediverse inside the Fediverse. These questions led to discussions about identity and structure. Is the Fediverse a single social network, or is it a collection of independent social spaces connected by shared protocols? Not new discussions, but old ideas surfacing. The point is the friction.</p>
<p>After a few years in the fediverse, I am in the second camp. Each instance is its own social network. Federation connects them, but it does not flatten their cultures, values, or social norms.g</p>
<p>This brings me to a belief I have held, and often repeated, like a parrot, for a long time. We used to say that it does not really matter which instance you choose when joining the Fediverse. Pick one, get started, and move later if you want. I still think this advice is useful for lowering the barrier to entry. But I no longer think it tells the full story.</p>
<p>Choosing an instance matters more than we tend to admit.</p>
<p>I have what Mike Cue called maxifediversed. I maintain multiple accounts across different servers, mainly to test features for <a href="https://badgefed.org">BadgeFed</a>, but also to observe how each space feels. What I noticed is that the differences go far beyond software features or moderation rules. The experience itself changes.</p>
<p>My main account is on <a href="https://hachyderm.io/@mapache">hachyderm.io</a>. It feels curated and thoughtful, but it also carries a lot of political weight. My account on mastodon.social feels faster and noisier, closer to what X used to feel like, and often better suited for shitposting. The Forkiverse feels new, friendly, and optimistic. At the same time, I already see signs of people who are enthusiastic about AI and who are looking for a more mainstream experience. That energy will inevitably clash with parts of the broader Fediverse, and that tension is part of the experiment, not a failure.</p>
<p>If you join an instance like infosec or dotnet.social, your experience will be different from the very beginning. The first people you interact with will shape your early network. The tone of conversations, the shared references, and what feels encouraged or discouraged will influence how you participate. Those early connections matter more than we usually acknowledge.</p>
<p>Of course, you are still you. Over time, as you follow people across instances and build your own connections, many experiences begin to converge. You find familiar voices. You establish your rhythm. But that convergence is never complete. The instance you call home continues to shape what you see, who discovers you, and how conversations evolve.</p>
<p>The Forkiverse makes all of this visible because it is unfolding in real time. We are watching a community take shape, with excitement, disagreement, and inevitable growing pains. Whether or not it fixes the internet is almost beside the point. What it clearly shows is that in the Fediverse, community is not abstract.</p>
<p>Community matters more than algorithms. Community is what makes the Fediverse diverse and resilient. It is intentional, contextual, and deeply shaped by where you choose to belong. And there is space, even if you have too many faces.</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>Fediverse</category>
      <category>Mastodon</category>
      <category>Forkiverse</category>
      <category>Social Media</category>
      <category>Creator Economy</category>
      <category>Communities</category>
      <category>Decentralization</category>
      <category>Tech Philosophy</category>
      <category>Building in Public</category>
    </item>
    <item>
      <title>Single Agent vs Multi-Agent: The Architectural Decision That&apos;ll Make or Break Your AI Project</title>
      <link>https://maho.dev/2025/11/single-agent-vs-multi-agent-the-architectural-decision-thatll-make-or-break-your-ai-project/</link>
      <pubDate>Mon, 10 Nov 2025 00:00:00 +0000</pubDate>
      <guid>https://maho.dev/2025/11/single-agent-vs-multi-agent-the-architectural-decision-thatll-make-or-break-your-ai-project/</guid>
      <description>The brutal truth about choosing between single and multi-agent architectures for your AI project. Complete with battle scars, unofficial Microsoft insights, and the harsh reality that agents are just fancy microservices with commitment issues.</description>
      <content:encoded><![CDATA[<h1 id="single-agent-vs-multi-agent-the-architectural-decision-thatll-make-or-break-your-ai-project">Single Agent vs Multi-Agent: The Architectural Decision That'll Make or Break Your AI Project</h1>
<p>We have a new project where we're going to build... yes, you got it—an agent. Or maybe many agents? That's exactly the question we needed to answer, and spoiler alert: the answer isn't as obvious as your last Tinder swipe.</p>
<p>We conducted thorough research and design sessions to determine the best approach. Here's what we found, complete with the battle scars and &quot;oh shit&quot; moments that came with it. I hope you can benefit from our findings without stepping on the same architectural landmines.</p>
<p>When building AI-powered systems, one of the fundamental architectural decisions is whether to implement a single comprehensive agent or multiple specialized agents working together. This choice significantly impacts performance, maintainability, user experience, and—let's be honest—your sanity as a developer.</p>
<p>But first, let's establish what we mean by an AI agent, because apparently everyone and their startup has a different definition.</p>
<h2 id="what-is-an-agent">What is an Agent?</h2>
<p>In the context of AI, specifically generative AI, an agent is software that can perform actions semi-autonomously or fully autonomously based on a set of instructions and can interact using natural language. Think of it as ChatGPT that actually <em>does</em> things instead of just talking about doing things.</p>
<p>Modern AI agents are built on Large Language Models (LLMs) enhanced with function calling capabilities. This functionality allows chatbots to perform actions beyond text generation—they can execute code, interact with APIs, modify files, and integrate with external systems. What started as simple Python code execution has evolved into sophisticated systems like Model Context Protocol (MCP) servers that enable agents to interact with virtually any external service. It's like giving your chatbot hands, and sometimes those hands have hammers.</p>
<p>So, what exactly defines an agent? The definition is somewhat fluid (shocking, I know—tech definitions being vague), but in my view, an AI agent is software that can:</p>
<ul>
<li>Execute actions semi-autonomously or fully autonomously</li>
<li>Follow a set of instructions or goals</li>
<li>Interact using human natural language</li>
<li>Make decisions based on context and feedback</li>
</ul>
<p>This broad definition could include everything from a grammar correction tool to a comprehensive marketing assistant—the possibilities are extensive, and the marketing departments (and CEOs with quarterly &quot;optimization&quot; <em>coff coff, layoffs</em> targets) are salivating.</p>
<p>The key distinction in development environments like VS Code is between &quot;Ask&quot; mode (traditional chat) and &quot;Agent&quot; mode. In Ask mode, the AI can only provide suggestions and answers—it's basically a very expensive rubber duck. In Agent mode, it can automatically modify files, execute commands, and potentially burn down your codebase in a blink if not properly configured. This autonomous capability is what makes agents both powerful and potentially the reason you'll be working weekends.</p>
<h2 id="what-is-a-multi-agent-design">What is a Multi-Agent Design?</h2>
<p>A single agent is essentially a monolithic piece of software—one component handling all functionality. It's the AI equivalent of that one coworker who insists they can handle &quot;everything&quot; and usually can't.</p>
<p>A multi-agent system consists of multiple specialized agents communicating with each other to accomplish complex tasks. The comparison to microservices is apt here, though agents don't need to be &quot;micro&quot;—hence the term &quot;multi-agent&quot; rather than &quot;micro-agent.&quot; Because apparently we learned nothing from the microservices hype cycle.</p>
<p>I researched what others in the field are saying about this. For instance, my colleague has written extensively about agent sizing, arguing that micro-agents don't make sense due to orchestration complexity, while overly large agents become difficult to manage. There should be clear principles for determining when an agent becomes too large—basically, the &quot;you know it when you see it&quot; approach to software architecture.</p>
<p>One critical limitation is function calling capacity. Agents typically become overwhelmed when dealing with more than 10-20 function calls. They start selecting inappropriate tools and their performance degrades significantly. This happens because function calling relies on tool descriptions, and it's much easier to choose between 2-3 tools than among 100 options. It's decision paralysis, but for robots.</p>
<p>The technical implementation compounds this problem. Since LLMs are stateless, you must pass complete function descriptions, parameters, and arguments as extensive JSON in the context window for every request. There's no magic here—<strong>the model literally reads through all available function definitions and tries to match them to your request</strong>. While companies often absorb or hide these token costs, the underlying issue remains: LLMs get cognitively overwhelmed when parsing through dozens of function options, leading to poor selection decisions. It's like having to reintroduce yourself and explain every tool in your toolbox at every meeting because your AI colleague has the memory retention of a particularly forgetful goldfish.</p>
<h2 id="so-which-approach-is-better">So Which Approach is Better?</h2>
<p>I've built agents before, but nothing as complex as what we're planning for production. The tooling considerations alone are substantial.</p>
<p>According to conversations with Microsoft engineers (though this isn't officially validated, so take it with a grain of corporate salt), Microsoft deliberately chose not to create a single orchestrating agent in Copilot. Instead, in Copilot Studio, you create multiple independent specialized agents, and users select which one they want to interact with. This approach is primarily driven by UX considerations—humans are naturally wired to interact with specific experts rather than some omnipresent AI overlord who claims to know everything. It eliminates the need for complex orchestration while providing a more intuitive user experience: just create domain-specific agents and let users pick their poison.</p>
<p>And for a reason, the multi-agent approach has its own complexities. Drawing from my experience with <a href="https://www.youtube.com/watch?v=S-p271bm7pw">monolith vs microservices</a> (where 90% of the time the answer is &quot;it depends,&quot; and the other 10% is &quot;you should have asked this question six months ago&quot;), there's a crucial difference here: agents are non-deterministic.</p>
<p>In microservices, you can reliably test individual units and perform integration testing to ensure predictable behavior. With agents, this becomes significantly more challenging—it's like trying to unit test a creative writing class. Microsoft engineers shared an interesting case where a single agent worked perfectly in isolation, but failed spectacularly when integrated into a multi-agent system. The orchestrating agent was inadvertently filtering out critical pieces of user request data that the specialized agent needed to function properly. It's the AI equivalent of playing telephone with important business requirements.</p>
<h2 id="trade-offs-to-consider">Trade-offs to Consider</h2>
<p>Here's the brutal reality check you've been waiting for:</p>
<p><strong>Multi-agent challenges:</strong></p>
<ul>
<li>Additional observability, logging, security, and tracing complexity (because debugging one black box wasn't hard enough)</li>
<li>Each agent layer adds an extra LLM call, increasing latency and cost (your AWS bill will thank you)</li>
<li>Non-deterministic behavior makes testing and debugging difficult (good luck explaining this to QA)</li>
<li>Orchestration complexity can introduce unexpected failure modes (surprise! Your AI just became sentient and decided to take a coffee break)</li>
</ul>
<p><strong>Single agent challenges:</strong></p>
<ul>
<li>Function calling limitations (performance degrades beyond 10-20 functions like a smartphone after two years)</li>
<li>Increased complexity in a monolithic structure (the &quot;god object&quot; problem, but with AI)</li>
<li>Harder to maintain and update specific capabilities (changing one thing breaks three others)</li>
<li>Context window limitations with extensive tool descriptions</li>
</ul>
<h2 id="recap-and-recommendations">Recap and Recommendations</h2>
<p>After thorough research, analysis, and a few existential crises about the nature of artificial intelligence, here's what I recommend:</p>
<h3 id="choose-single-agent-when">Choose Single Agent When:</h3>
<ul>
<li>Your use case requires fewer than 15-20 function calls</li>
<li>You need predictable, deterministic behavior (good luck with that)</li>
<li>You're building a focused, domain-specific solution</li>
<li>You want to minimize latency and cost</li>
<li>Your team has limited experience with agent orchestration</li>
<li>You value your sleep and mental health</li>
</ul>
<h3 id="choose-multi-agent-when">Choose Multi-Agent When:</h3>
<ul>
<li>You need to handle diverse, complex workflows that exceed function calling limits</li>
<li>You have distinct domains that require specialized expertise</li>
<li>You can afford the additional complexity and cost (both financial and psychological)</li>
<li>You have robust observability and testing infrastructure</li>
<li>User experience benefits from specialized interactions</li>
<li>You enjoy debugging distributed systems with non-deterministic behavior</li>
</ul>
<h3 id="key-takeaways">Key Takeaways</h3>
<ol>
<li><p><strong>Start Simple</strong>: Begin with a single agent and evolve to multi-agent only when you hit clear limitations. It's easier to split than to merge, unlike your last relationship.</p>
</li>
<li><p><strong>Function Limits Matter</strong>: The 10-20 function calling threshold is real and impacts performance significantly. Respect the limit, or the limit will disrespect you.</p>
</li>
<li><p><strong>Non-deterministic Nature</strong>: Unlike microservices, agents require different testing and debugging approaches. Traditional testing strategies go out the window faster than your productivity after discovering TikTok.</p>
</li>
<li><p><strong>Cost Considerations</strong>: Each agent layer adds LLM calls—budget accordingly. Your CFO will either love you or hate you, probably both.</p>
</li>
<li><p><strong>Orchestration is Hard</strong>: Multi-agent coordination introduces complex failure modes that would make a Byzantine general weep.</p>
</li>
</ol>
<p>The decision ultimately depends on your specific requirements, team capabilities, and tolerance for complexity. Like the microservices debate, there's no universal answer—but understanding these trade-offs will help you make an informed choice instead of a panicked one at 2 AM when everything's on fire.</p>
<p><strong>Remember: you can always start with a single agent and refactor to multi-agent as your requirements evolve.</strong> This is what we are going to do. The key is building with future flexibility in mind while avoiding premature optimization. Because the only thing worse than over-engineering is under-engineering and then frantically re-engineering when your simple solution becomes the bottleneck for your entire product.</p>
<p>Choose wisely, debug extensively, and may the odds of deterministic behavior be ever in your favor.</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>AI Agents</category>
      <category>Software Architecture</category>
      <category>LLMs</category>
      <category>Function Calling</category>
      <category>Multi-Agent Systems</category>
      <category>Microservices</category>
      <category>Software Engineering</category>
      <category>AI Development</category>
      <category>Technical Decision Making</category>
      <category>Agent Orchestration</category>
      <category>Microsoft Copilot</category>
      <category>Development Experience</category>
    </item>
    <item>
      <title>VocalCat: Why it went open source</title>
      <link>https://maho.dev/2025/09/vocalcat-why-it-went-open-source/</link>
      <pubDate>Tue, 30 Sep 2025 01:24:39 +0000</pubDate>
      <guid>https://maho.dev/2025/09/vocalcat-why-it-went-open-source/</guid>
      <description>The story behind why I decided to open-source VocalCat—my social media management suite for creators. It&apos;s about ego checks, building in public, and staying true to open-source values.</description>
      <content:encoded><![CDATA[<h1 id="vocalcat-why-it-went-open-source">VocalCat: Why It Went Open Source</h1>
<h2 id="from-toy-to-framework">From Toy to Framework</h2>
<p>At some point, every developer has this moment: you start with a couple of scripts. Some command-line helpers. Then you add a CLI. Then some templates. A half-baked web UI I slapped on top.</p>
<p>At some point, it crossed that invisible line. Suddenly, it’s not “my random collection of hacks” anymore. It’s a <strong>framework.</strong>
That’s exactly how <strong>VocalCat</strong> was born.</p>
<p>Originally, it was just a suite of tiny automations I needed for my own social media workflow. Over time, it became more than a toy, more than a set of tools—closer to a fully fleshed-out side project.</p>
<hr />
<h2 id="naming-things-is-hard-and-fun">Naming Things Is Hard (And Fun)</h2>
<p>When it got serious, it needed a name. I bought a couple of domains—<strong>vocalfox</strong> and <strong>vocalcat</strong>—and even entertained names like <code>we-eat-cats</code> and <code>a-whisker-wave</code> (don’t ask).</p>
<p>I leaned toward <em>vocalcat</em> because it felt quirky but memorable. And since I was already building in public, I started posting about logo ideas, naming debates, and the messy in-between decisions most people never share.</p>
<hr />
<h2 id="building-in-public-but-also.ego">Building in Public, But Also… Ego</h2>
<p>Here’s the honest bit.</p>
<p>For a while, I wanted VocalCat to be mine. My little Frankenstein. My secret sauce. The thing that worked because I built it. That’s ego.</p>
<p>I wanted VocalCat to succeed quietly—like some secret sauce I had all to myself. Again, that’s ego talking.</p>
<p>But at the same time, I’ve always been an open-source person at heart. And ego doesn’t scale. Ego doesn’t invite collaboration. Ego doesn’t let people build with you. Reconciling those two instincts—<em>“keep it private”</em> vs <em>“throw it to the wild”</em>—was the tension that pushed me to make a choice.</p>
<p>So I asked myself: what’s the actual value? Keeping it locked away? Or putting it out there so anyone can learn, break it, improve it, or even fork it?</p>
<p>The answer was obvious.</p>
<hr />
<h2 id="whats-the-real-value">What’s the Real Value?</h2>
<p>The real value wasn’t in hoarding the code. It was in:</p>
<ul>
<li><strong>Dogfooding my own workflow</strong></li>
<li><strong>Rejecting pointless automation</strong> (no generic AI spam here)</li>
<li><strong>Focusing on creators, not faceless content farms</strong></li>
</ul>
<p>If VocalCat helps even one small creator publish work with intention instead of noise, then it’s already successful.</p>
<p>VocalCat didn’t appear fully formed. It evolved.</p>
<p>First it automated the boring parts of my own workflow.</p>
<p>Then it grew a web interface.</p>
<p>Then I bolted on publishing across platforms.</p>
<p>And recently? I turned it into an MCP server.</p>
<p>That means you can publish to all your socials straight from your editor—say, VS Code—or even from Copilot itself. No detour, no extra UI, just “write → ship.”</p>
<p>This isn’t vaporware. It works because I’ve been dogfooding it every day.</p>
<hr />
<h2 id="customer-zeros-you-rock">Customer Zeros, You Rock</h2>
<p>The best part of this journey has been my “customer zeros.”</p>
<p>The early folks who were willing to try something half-finished. Who gave me unfiltered feedback. Who didn’t care that the docs were rough or that the logo was a cat I drew in five minutes.</p>
<p>Tools like Make or Honeybee made this easier to stitch together—but it’s the humans at the other end who shaped it into more than a toy.</p>
<p>Without them, VocalCat would still be a bunch of lonely scripts on my machine.</p>
<h2 id="a-saturated-market-of-alternatives">A Saturated Market of Alternatives</h2>
<p>Now, let’s be real for a second: the social media tooling space is brutally saturated.</p>
<p>My “market research” (read: late-night Googling) turned up plenty. There are dozens—if not hundreds—of tools promising to “automate everything,” “maximize engagement,” or “10x your content pipeline.”</p>
<p>And honestly? They’re great. If you want bulk uploads, AI-generated captions, and auto-scheduling across a thousand accounts, you’re spoiled for choice.</p>
<p>But that’s not what gets me out of bed in the morning.</p>
<p>VocalCat is for the scrappy individuals like me who wants something more thoughtful. But mainly, VocalCat is for creators who want something a little more deliberate. Less noise. More intent.</p>
<hr />
<h2 id="why-open-source">Why Open Source?</h2>
<p>Because that’s where it belongs.</p>
<p>I don’t want to build another walled garden. I want to build something transparent, trustworthy, and communal. Open source lets anyone inspect it, contribute to it, or just learn from it.</p>
<p>And more importantly: open source keeps me honest. If the code sucks, you’ll see it. If the design is smart, you’ll see that too.</p>
<p>The truth is, VocalCat isn’t a billion-dollar SaaS. It’s a passion project. And passion projects thrive in the open.</p>
<hr />
<h2 id="whats-next">What’s Next?</h2>
<p>Honestly? I don’t know.</p>
<p>I’ve taken it from scripts to suite, from private toy to public project, and from ego trip to open source. At the end, VocalCat isn’t trying to be the everything-tool for everyone. It’s not about “growth hacking” or “maximizing engagement.”</p>
<p>It’s about creators making work that matters. And open source is the only way that vision makes sense—because the internet needs more <em>shared tools</em> and fewer <em>walled gardens.</em></p>
<p>What’s next should come from the community. Maybe someone will add integrations I never imagined. Maybe the project will die and will be back to my personal tool. Maybe someone will fork it into something I couldn’t dream up. Maybe it’ll stay small but useful.</p>
<p>That’s the beauty of putting it out there: the story doesn’t just belong to me anymore.</p>
<p>So that is why VocalCat is now open. The code, the vision, the rough edges. What happens next—I hope you’ll help decide.</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>VocalCat</category>
      <category>Open Source</category>
      <category>Social Media</category>
      <category>Creator Tools</category>
      <category>Side Projects</category>
      <category>Software Development</category>
      <category>Building in Public</category>
      <category>Tech Philosophy</category>
      <category>Ego Check</category>
    </item>
    <item>
      <title>F*ck Yeah, There&apos;s Still a Human in the Loop</title>
      <link>https://maho.dev/2025/07/fck-yeah-theres-still-a-human-in-the-loop/</link>
      <pubDate>Tue, 15 Jul 2025 01:24:39 +0000</pubDate>
      <guid>https://maho.dev/2025/07/fck-yeah-theres-still-a-human-in-the-loop/</guid>
      <description>AI is taking over everything—except the parts that still need soul. Here’s why the future of software still needs people, not just prompts.</description>
      <content:encoded><![CDATA[<blockquote>
<p>&quot;The factory of the future will have only two employees, a man and a dog. The man will be there to feed the dog. The dog will be there to keep the man from touching the equipment.&quot; – Warren G. Bennis</p>
</blockquote>
<p>The first piece of software I ever sold was to a local hardware store.</p>
<p>I can still remember the smell of that place: a mix of sawdust, metal, and paint thinner. Their inventory was a glorious, chaotic mess, tracked on paper and by the owner's memory. He needed a better way.</p>
<p>So, we (me and a high-school friend) built him one. A simple CRUD app in Visual FoxPro. It wasn't pretty. It probably had more bugs than a summer night the beach of San Blas, Mexico. But it worked. Seeing the owner use it for the first time, seeing his relief as he could finally track his nuts, bolts, and screws—was a revelation. I had taken a real-world problem and solved it with nothing but a keyboard and my brain.</p>
<p>That feeling is why I fell in love with coding. It’s a mix of craft, art, and mastery.</p>
<p>And it’s that feeling that has me thinking about our future.</p>
<p>Then, a few weeks ago, I stumbled upon a mesmerizing ASMR video of a master woodworker making tiles. This guy wasn’t just making tiles; he was conducting a symphony of efficiency. He felled a tree, milled the wood, and meticulously shaped each piece, transforming raw timber into a perfect stack. Every movement was precise, honed by years of practice to maximize his materials and work with incredible speed.</p>
<p><img src="/img/artetablillas.jpg" alt="Handmade wooden tiles" /></p>
<p>I saw myself in him. I saw that kid in the hardware store, wrestling with Visual FoxPro. It was the same spark.</p>
<p>That video was a lightbulb moment. One day, coding by hand with nothing but a reference book will be seen just like that—a beautiful craft, a hobby, maybe even a performance art. But building commercial apps? That’s going to be a different beast entirely, just like manufacturing tiles is different from that woodworker's art. The designers and builders will still be essential, but their jobs will be unrecognizable. The skills they need will have shifted dramatically.</p>
<p><img src="/img/maho_pacheco_pepper.jpg" alt="Maho Pacheco with Pepper the robot" /></p>
<p>Carpentry wasn't eliminated, just as grocery cashiers haven't completely disappeared. But their jobs have evolved. Cashiers now often manage inventory, handle customer relations, and even recognize theft patterns. Woodworkers now need to understand industrial design, LEAN techniques, and how to operate sophisticated equipment. The fundamentals remain, knowledge of wood, materials, and problem-solving, just as software engineering will always require a grasp of its core principles.</p>
<p>There will always be a human in the loop. But this human is not just a passive observer, like the man watching the dog. They will be a master of system design, a strategic thinker with a deeper understanding of the entire process. AI will augment their capabilities, not replace them.</p>
<p>The whole &quot;Gen-AI will replace coders&quot; panic feels like déjà vu. Remember 15 years ago? Every car manufacturer and hotshot startup was promising fully autonomous driving in 3-5 years. Sound familiar? It’s the same breathless hype we’re hearing from some AI CEOs today, talking about replacing coders in six months or a &quot;single-digit number of years.&quot; Well, 15 years later, Waymo is still getting stumped by a chaotic street protest. The auto industry was absolutely disrupted—we got amazing cruise control and semi-autonomous features out of it—but it wasn't the total revolution we were sold. The software engineering industry has been disrupted too, but it's going to need a human in the driver's seat for many years to come.</p>
<p>The question is, what kind of humans will be needed? It's time to prepare and upskill in the areas that will position you to be the one who matters.</p>
<p>As for me, I imagine a future where we look back at coders who worked without Gen-AI, using only a reference manual, as artisans. Perhaps they’ll have YouTube channels, and people will watch in awe as they create a fun application without a search engine or a copilot. It will be a fascinating glimpse into a lost art.</p>
<p>And maybe, just maybe, some kid will be in a local shop, see a problem, and feel that same spark I did in that hardware store all those years ago.</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>AI</category>
      <category>Software Development</category>
      <category>Future</category>
      <category>Human in the Loop</category>
      <category>Dev Philosophy</category>
      <category>AI Ethics</category>
      <category>Coding with AI</category>
      <category>Tech Culture</category>
      <category>Human Touch</category>
      <category>Automation</category>
    </item>
    <item>
      <title>MCP Expert Zone: Common Questions from Microsoft Build 2025</title>
      <link>https://maho.dev/2025/05/mcp-expert-zone-common-questions-from-microsoft-build-2025/</link>
      <pubDate>Fri, 23 May 2025 01:24:39 +0000</pubDate>
      <guid>https://maho.dev/2025/05/mcp-expert-zone-common-questions-from-microsoft-build-2025/</guid>
      <description>A recap of the most frequently asked questions about the Model Context Protocol (MCP) from my time as an expert at the MCP booth during Microsoft Build 2025. Covers what MCP is, how it works, authentication, differences from OpenAPI, agent-to-agent communication, and practical advice for developers.</description>
      <content:encoded><![CDATA[<p>I was a designated expert for a few shifts on the MCP booth at Microsoft Build 2025. Some people call it booth duty, it's not glamorous; you stand there waiting for people to come and ask questions.</p>
<p>However, because Satya and others were constantly giving demos and mentioning MCP every few seconds, this was one of the most visited booths. I got my fair share of people.</p>
<p>I didn't think I needed to do yet another blog post about MCP, after all, there are already hundreds of resources out there, but in the spirit of recollection, here are the most common questions I got this week. I hope some people find it useful.</p>
<p><img src="/img/mcpbuild25.jpg" alt="The MCP Booth at Microsoft Build 2025" /></p>
<hr />
<h2 id="what-is-mcp"><strong>What is MCP?</strong></h2>
<p>The most common question I received was either, &quot;What is MCP?&quot; or, &quot;I think I understand MCP, but I want to confirm.&quot;</p>
<p>I first answered as an analogy:</p>
<ul>
<li><strong>Analogy:</strong> MCP is a spec/protocol that allows agents to perform actions outside their realm of &quot;thinking.&quot; Think of it like adding skills to Alexa: by default, Alexa can answer questions, but to control your Samsung oven, you need to add a skill. Similarly, MCP lets agents do more by exposing new capabilities.</li>
</ul>
<p>Then I used to get:</p>
<h2 id="but-what-is-mcp-is-mcp-like-http-is-it-an-app-a-layer"><strong>But WHAT is MCP? Is MCP like HTTP? Is it an app? A layer?</strong></h2>
<ul>
<li><strong>Technical:</strong> MCP is a protocol—a specification for communication between agents. It's not a network protocol like HTTP or FTP, but more like ATProto (behind Bluesky) or ActivityPub (behind the Fediverse). In remote scenarios, MCP runs over HTTP (ports 80, 443, etc.), using endpoints like <code>/sse</code> and <code>/message</code>, and defines how to expose tools, resources, receive messages, and manage sessions.</li>
</ul>
<p>People seems to get confused by the word <em>protocol</em>, so I reiterated that it was <strong>not a network protocol</strong>.</p>
<hr />
<h2 id="how-it-works"><strong>How it works?</strong></h2>
<p>You implement an MCP server that exposes tools (APIs, functions, or resources) and follows the MCP protocol for message exchange. MCP defines a standard way for agents (like AI assistants or bots) to communicate and perform actions. Agents connect to your server, discover available tools, and send or receive messages using the defined endpoints. Sessions are maintained (often via Server-Sent Events for remote servers), and agents can poll or stream updates. An MCP client can, for example, ping at regular intervals to retrieve new tools.</p>
<p>It can work in two ways—local or remote—but the specification and the way to expose tools (APIs, functions, prompts, or resources) are the same: the MCP protocol for message exchange.</p>
<hr />
<h2 id="how-much-does-mcp-cost-how-can-i-enable-it-in-my-azure-portal"><strong>How much does MCP cost? How can I enable it in my Azure Portal?</strong></h2>
<p>MCP does not cost anything. It's an open specification—anyone can implement it, either as a client or a server. There is nothing to enable in the Azure Portal (one customer even asked me to look for MCP there); instead, you host an MCP server yourself, such as in a Web App, HTTP service, or Azure Functions.</p>
<hr />
<h2 id="how-can-i-bring-my-whole-legacy-data-or-100-tables-of-databases-to-mcp"><strong>How can I bring my whole legacy data or 100 tables of databases to MCP?</strong></h2>
<p>You don't.</p>
<p>Before jumping to answer this question (or similar ones), I drill down into the why. What is the purpose? In MCP, like any other tool (GraphQL, BigData, OpenAPI, etc.), it is just one more tool. What you really need to define—and this depends on your scenario—are questions like: Who is the consumer? How are you limiting data to certain parts? Are they only reading or writing? How fast do you need it? How are you partitioning? What level of granularity do you want to expose (entire tables, specific queries, or business operations)? How will you handle data privacy and compliance requirements? What monitoring or auditing will you need for agent access to your data?</p>
<p><strong>You don't need to expose your entire database.</strong> Instead, you create MCP tools that wrap the specific actions or queries you want agents to perform (e.g., “GetCustomerById&quot; or “ListOrders&quot;). MCP is about exposing capabilities, not raw data.</p>
<p>Bringing the data is fairly straightforward, but we should think on the why before jumping to the solution. I expect tools like Microsoft Fabric to provide MCP servers soon, so hopefully this part is solved.</p>
<hr />
<h2 id="how-can-i-do-x-type-of-authentication"><strong>How can I do (X) type of authentication?</strong></h2>
<p>MCP itself doesn't mandate a specific authentication method. You can use standard HTTP authentication mechanisms (OAuth, API keys, JWT, user/password, Microsoft Entra, etc.) at your endpoints. If you're using an existing Agent MCP client (like VSCode, Cursor, Copilot Connectors), you're limited to the auth mechanisms it supports. For custom scenarios, you can handle authentication externally and pass tokens to your local MCP server.</p>
<p>I often used extreme examples to illustrate this. For instance, if you needed a unique authentication mechanism—say, verifying a user by having them dance and sing on video—you would handle that authentication externally in your own app. After validating the user, you would store the resulting tokens and pass them to your local MCP server.</p>
<p>I compared this to how <code>az login</code> (the Azure CLI utility) works: it opens a browser for authentication, stores credentials locally, and then uses them to connect to Azure. Similarly, your local MCP server can manage custom authentication, store tokens, and allow tools like VSCode to use those tokens for access. This is essentially how the Azure MCP server operates.</p>
<p>While this approach can be cumbersome—since it may require users to install software locally—solutions like Docker have helped streamline the process. In the future, I expect VSCode and other agentic clients to offer more integrated authentication options. You could even create a VSCode extension for your custom authentication method and pass the tokens into the configuration's <code>$input</code> field.</p>
<hr />
<h2 id="how-can-i-be-the-official-mcp-server-for-x"><strong>How can I be the official MCP server for X?</strong></h2>
<p>There's no central registry for “official&quot; MCP servers, but you can list your server at <a href="https://github.com/modelcontextprotocol/servers">the official server list</a> for visibility. Discovery is organic and depends on each MCP client implementation. In the future, agent platforms (e.g Copilot Studio) may have their own directories or marketplaces.</p>
<hr />
<h2 id="what-is-the-difference-between-openapi-or-rest-api-or-soap-etc.and-mcp-why-cant-i-just-use-openapi"><strong>What is the difference between OpenAPI (or REST API, or SOAP, etc.) and MCP? Why can't I just use OpenAPI?</strong></h2>
<p>Sure you can. You can expose OpenAPI schemas to agents, and many people do this to enable agent actions. However, MCP is fundamentally different and simpler, representing a new paradigm.</p>
<p>OpenAPI describes REST APIs for both humans and machines, but it doesn't define how agents should dynamically discover, interact with, or invoke tools. MCP, on the other hand, is purpose-built for agent-to-agent or agent-to-tool communication, with conventions for discovery, invocation, and session management. While OpenAPI is focused on CRUD operations and endpoint definitions (and most of the time single stateless requests), MCP is more intent-driven—its tools are designed to map directly to user intents rather than just exposing low-level endpoints.</p>
<p>In few words, <strong>OpenAPI is schema-first and stateless, while MCP is intent-first and session-aware.</strong></p>
<p>For example, in a typical REST API for an e-commerce scenario, you might need to call several endpoints to complete a checkout (e.g., <code>GET /products</code>, <code>GET /product/{id}</code>, <code>POST /cart</code>, <code>PUT /cart/checkout</code>). In MCP, you could expose a single tool like <code>addProductToCartAndCheckout</code>, which encapsulates the entire user intent in one action. This makes MCP tools more aligned with how agents interpret and fulfill user requests.</p>
<p>In practice, I've added C# annotations to an existing HTTP REST API and was able to expose both REST and MCP interfaces from the same service, serving different types of clients. However, to get the most out of MCP, you should design your tools thoughtfully; just as you wouldn't map business entities 1:1 to database tables or REST endpoints, you shouldn't map every REST endpoint directly to an MCP tool. The goal is to expose meaningful capabilities that match user or agent intents.</p>
<hr />
<h2 id="can-i-use-mcp-to-communicate-between-agent-to-agent"><strong>Can I use MCP to communicate between Agent-to-Agent?</strong></h2>
<p>Absolutely. MCP is designed to support agent-to-agent communication. For example, you could have one agent dedicated to handling CRM-related intents (such as Salesforce requests). This agent might expose a single MCP tool (like &quot;CRM handler&quot;), which receives natural language requests from other agents. The CRM agent then uses MCP to communicate with the underlying Salesforce MCP server and fulfill the request. Because MCP is a protocol and specification, you can extend it to fit custom scenarios and workflows, enabling flexible agent-to-agent interactions.</p>
<hr />
<h2 id="how-does-an-agent-know-which-mcp-tool-to-call"><strong>How does an agent know which MCP tool to call?</strong></h2>
<p>I don't have insights over how internally VSCode or other MCP clients handle that call. Of course, if you implement your own MCP client you would have complete control over it. What I know is that description is super important. It is a shame, that you cannot provide few-shots to VSCode <em>yet</em>, or other tools to make it more deterministic. I could think in 3 strategies of how to route calls:</p>
<ol>
<li>NLP, tokenizing, using utterances and trying to detect the user intent. Not different that how LUIS and bot framework use todo it.</li>
<li>Vector embeddings, storing the MCP tools in a vector embeddings database, and finding the closest one to the user request.</li>
<li>LLM matching, asking to an LLM to find the closest match (which is at the end an expensive modification of vector embeddings).</li>
</ol>
<p>Customers and people seems to try to find to make it more deterministic.</p>
<hr />
<h2 id="where-do-i-start"><strong>Where do I start?</strong></h2>
<p>We shared this <a href="https://github.com/microsoft/mcp-for-beginners">repo as the holy grail of MCP</a> and recommend looking at the sample implementations. Try exposing a simple tool (like a “hello world&quot; function) as an MCP server, then connect an agent to it. Don't be misled by the name &quot;beginners&quot; in the title, &quot;mcp-for-beginners&quot; repo is comprehensive, covering everything from the basics to advanced concepts and early insights from real-world implementation.</p>
<hr />
<h2 id="is-it-good-to-invest-in-mcp-for-my-company"><strong>Is it good to invest in MCP for my company?</strong></h2>
<p>If you want to make your services accessible to AI agents or participate in the growing ecosystem of agent-based automation, MCP is a good investment. It's open, flexible, and designed for interoperability. Early adoption can be advantageous.</p>
<hr />
<h2 id="how-do-i-distribute-my-mcp-server-to-usersdeveloperscustomers-how-can-they-find-me"><strong>How do I distribute my MCP server to users/developers/customers? How can they find me?</strong></h2>
<p>You can publish your MCP server's endpoint and documentation on your website, developer portal, or relevant directories. As the ecosystem grows, more discovery mechanisms will emerge (like registries or marketplaces). For now, clear documentation and outreach are key. Some share configuration files (like <code>mcp.json</code> for VSCode) directly with developers (I don't recommend this).</p>
<p>Anthropic is actively collaborating on the development of an official MCP Registry, as outlined in the <a href="https://modelcontextprotocol.io/development/roadmap#registry">MCP roadmap</a>. This registry aims to provide a centralized directory for MCP servers, making it easier for agents and developers to discover, register, and connect to available MCP endpoints. While this would allow to have private registries, or it will be only a public one, I don't know.</p>
<hr />
<h1 id="final-thoughts"><strong>Final thoughts</strong></h1>
<p>MCP isn't complicated, but it does require intentional design. The key is to focus on exposing meaningful capabilities, not just raw data or low-level endpoints. <strong>Think in terms of what an agent should be able to do, not just what your API can do.</strong></p>
<p>The ecosystem is evolving fast. What feels like a hack today might be a best practice tomorrow. We're already seeing early patterns emerge, like wrapping business operations as tools, using MCP for agent-to-agent orchestration, and integrating with developer environments like VSCode.</p>
<p>Experimentation isn't just encouraged—it's essential. Try building a small MCP server, wire it up to an agent, and see what happens. You'll learn more in a weekend of tinkering than in a week of reading docs. There is no better way to understand and click with MCP than by experimenting with it hands-on.</p>
<p>And if you're still unsure where to start, just remember: “hello world&quot; (or better, &quot;water my plants!&quot;) is a valid MCP tool.</p>
<p>If you have more questions or want to jam on ideas, reach out. I'd love to hear what you're building.</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>MCP</category>
      <category>Microsoft Build</category>
      <category>protocols</category>
      <category>AI agents</category>
      <category>agentic automation</category>
      <category>OpenAPI</category>
      <category>authentication</category>
      <category>Azure</category>
      <category>server</category>
      <category>developer experience</category>
    </item>
    <item>
      <title>Integrating MCP in a Real SaaS: VocalCat</title>
      <link>https://maho.dev/2025/05/integrating-mcp-in-a-real-saas-vocalcat/</link>
      <pubDate>Thu, 15 May 2025 01:24:39 +0000</pubDate>
      <guid>https://maho.dev/2025/05/integrating-mcp-in-a-real-saas-vocalcat/</guid>
      <description>In this blog post, I&apos;ll share how I integrated the Model Context Protocol (MCP) into a real SaaS product, VocalCat , by reusing an existing API. This guide will...</description>
      <content:encoded><![CDATA[<p>In this blog post, I'll share how I integrated the <strong>Model Context Protocol (MCP)</strong> into a real SaaS product, <strong><a href="https://www.vocalcat.com">VocalCat</a></strong>, by reusing an existing API. This guide will highlight the interesting bits, challenges, and tips I discovered along the way. Buckle up!</p>
<h2 id="why-mcp">Why MCP?</h2>
<p>MCP is an open standard that simplifies connecting AI models to external tools and data systems. In plain terms, MCP is a way to give &quot;hands&quot; to copilots—a protocol that lets AI models not just answer questions, but actually take actions, even in the real world. Imagine connecting your copilot to your IoT devices, so it can control things in your home or office. It's a bit dangerous, but incredibly exciting: suddenly, copilots can do more than just talk—they can act.</p>
<p>For <a href="https://www.vocalcat.com">VocalCat</a>, integrating MCP meant enabling seamless interaction between APIs designed to control and create content for social media. A simple example of how MCP can help is when I'm coding and want to share small pieces of advice or tricks. Often, I get so focused on coding that I forget to document or share these insights. With MCP, I could automate this process—perhaps by creating a tool that captures my thoughts as I code and formats them into a blog post draft.</p>
<p>In fact, this very blog post is a good example of how MCP can streamline workflows.</p>
<p><img src="/img/mcpvocalcat.png" alt="MCP integration example" /></p>
<h2 id="reusing-an-existing-api">Reusing an Existing API</h2>
<p>One of the most exciting aspects of this integration was reusing the existing <code>ApiController</code> API, which was built using <strong>ASP.NET Core</strong>. This API was meant to use in the traditional way, for our partner MeetNearMe. Instead of creating a new API from scratch, I added MCP annotations to the existing endpoints. This approach saved time and ensured consistency across the platform.</p>
<p>Here's an example of how I annotated an existing endpoint:</p>
<pre><code class="language-csharp">[McpServerTool, Description(&quot;Get social accounts for the user from VocalCat&quot;)]
[HttpGet(&quot;socials&quot;)]
public async Task&lt;IActionResult&gt; GetSocials()
{
    if (userId == null)
    {
        // Handle missing user ID
    }
    // Existing logic...
}
</code></pre>
<p>By adding the <code>[McpServerTool]</code> attribute, the endpoint became MCP-compatible without requiring significant changes to its core functionality. This approach contrasts with many MCP examples, which often involve creating new classes solely for MCP integration. It's worth noting that this codebase, running on a single Azure Web App, efficiently handles the frontend and backend (Blazor), API, and now MCP, all within a unified architecture.</p>
<h2 id="handling-authentication-with-mcp">Handling Authentication with MCP</h2>
<p>Authentication was a critical part of the integration. In MCP, sessions are long-lived, especially in <strong>Server-Sent Events (SSE)</strong> scenarios. This required a different approach to handling <code>HttpContext</code>.</p>
<p>Instead of using the <code>HttpContext</code> available in <code>ControllerBase</code>, I injected it via <strong>Dependency Injection (DI)</strong>. This ensured that the <code>HttpContext</code> was consistent across the entire session.</p>
<p>Here's how I implemented it:</p>
<pre><code class="language-csharp">private readonly IHttpContextAccessor _httpContextAccessor;

public MeetAuthController(IConfiguration configuration, IDbService dbService, IBlobService blobService, CreateContentService createContentService, IHttpContextAccessor httpContextAccessor)
{
    _configuration = configuration;
    _dbService = dbService;
    _blobService = blobService;
    _createContentService = createContentService;
    _httpContextAccessor = httpContextAccessor;
}

private async Task&lt;string?&gt; ValidateAndRetrieveUserId()
{
    var accessCode = _httpContextAccessor.HttpContext?.Request.Headers[&quot;X-AccessCode&quot;];
    
    // Validation logic...
}
</code></pre>
<p>This approach ensured that the authentication logic was robust and compatible with MCP's requirements.</p>
<h2 id="integrating-apis-and-mcp-in-a-blazor-app">Integrating APIs and MCP in a Blazor App</h2>
<p>One of the unique aspects of this Blazor app is how it seamlessly integrates APIs and MCP. The <code>Program.cs</code> file demonstrates how the app maps different functionalities, including traditional APIs and MCP endpoints.</p>
<h3 id="mapping-apis">Mapping APIs</h3>
<p>The app uses <code>MapGroup</code> to organize API endpoints under a common route. For example, all API endpoints are grouped under <code>/api</code>:</p>
<pre><code class="language-csharp">app.MapGroup(&quot;/api&quot;).MapControllers();
</code></pre>
<p>This approach keeps the API routes clean and organized, making it easier to manage and scale.</p>
<h3 id="mapping-mcp">Mapping MCP</h3>
<p>In addition to traditional APIs, the app integrates MCP using the <code>MapMcp</code> method. This maps all MCP-related endpoints under the <code>/mcp</code> route:</p>
<pre><code class="language-csharp">app.MapMcp(&quot;mcp&quot;);
</code></pre>
<p>By combining these two approaches, the app supports both standard API interactions and advanced MCP functionalities.</p>
<p>This dual mapping strategy highlights the flexibility of the app. It allows developers to leverage the strengths of both traditional APIs and MCP, enabling seamless integration with external tools and AI-powered systems.</p>
<h2 id="tips-and-tricks">Tips and Tricks</h2>
<h3 id="leverage-existing-tools">1. Leverage Existing Tools</h3>
<p>If you're integrating MCP into an existing project, look for opportunities to reuse existing APIs and services. Adding MCP annotations is often enough to make your endpoints compatible and avoid duplicating code.</p>
<h3 id="understand-sse">2. Understand SSE</h3>
<p>Server-Sent Events (SSE) play a crucial role in MCP. Unlike traditional HTTP requests, SSE sessions are persistent and require careful resource management, particularly with <code>HttpContext</code>. Injecting <code>HttpContext</code> via Dependency Injection (DI) ensures consistency and avoids potential issues during long-lived sessions.</p>
<p>MCP primarily operates through two endpoints: <code>/message</code> and <code>/sse</code>. The <code>/message</code> endpoint handles messages sent by the MCP server, while the <code>/sse</code> endpoint receives messages from the copilot.</p>
<h3 id="debugging-mcp">3. Debugging MCP</h3>
<p>Debugging MCP integrations can be tricky. Enable detailed logging in your application to capture MCP-specific errors.</p>
<h3 id="secure-your-endpoints">4. Secure Your Endpoints</h3>
<p>MCP endpoints often expose sensitive data. Use authentication and authorization mechanisms to ensure that only authorized users can access your APIs.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Integrating MCP into <a href="https://www.vocalcat.com">VocalCat</a> was a rewarding experience. By reusing existing APIs and adopting best practices, I was able to bring MCP closer to production with minimal effort. If you're considering MCP for your project, I hope this guide provides valuable insights and inspiration.</p>
<p>Have questions or want to share your own MCP integration story? Let me know in the comments or in my socials!</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>MCP</category>
      <category>Model Context Protocol</category>
      <category>SaaS</category>
      <category>ASP.NET Core</category>
      <category>VocalCat</category>
      <category>Web Development</category>
      <category>API Integration</category>
    </item>
    <item>
      <title>A Guide to Implementing ActivityPub in a Static Site (or Any Website) - Q1 2025 Updates</title>
      <link>https://maho.dev/2025/03/a-guide-to-implementing-activitypub-in-a-static-site-or-any-website-q1-2025-updates/</link>
      <pubDate>Thu, 13 Mar 2025 01:24:39 +0000</pubDate>
      <guid>https://maho.dev/2025/03/a-guide-to-implementing-activitypub-in-a-static-site-or-any-website-q1-2025-updates/</guid>
      <description>Bringing Static Sites to the Fediverse: Enhancements and Implementations Integrating static sites into this ecosystem via the ActivityPub protocol presents uniq...</description>
      <content:encoded><![CDATA[<h2 id="bringing-static-sites-to-the-fediverse-enhancements-and-implementations">Bringing Static Sites to the Fediverse: Enhancements and Implementations</h2>
<p>Integrating static sites into this ecosystem via the ActivityPub protocol presents unique challenges and opportunities. In this post, I share recent updates and discoveries from my journey to seamlessly connect static sites to the Fediverse.</p>
<h3 id="enhancing-content-delivery-with-customizable-templates">Enhancing Content Delivery with Customizable Templates</h3>
<p>One of the primary goals in integrating static sites with ActivityPub is to ensure that content is delivered effectively within the Fediverse. To achieve this, I've introduced customizable templates for note generation. This enhancement allows the full content of a blog post to be included directly in ActivityPub notes, moving beyond mere link sharing—a practice often associated with bots—and leveraging the full potential of the Fediverse.</p>
<p>This approach ensures that followers can read and engage with complete articles within their preferred platforms, without the need to open a new browser, enhancing the user experience and fostering deeper connections.</p>
<h3 id="navigating-signed-requests-in-activitypub-implementations">Navigating Signed Requests in ActivityPub Implementations</h3>
<p>During this integration journey, I encountered instances where certain servers require signed requests to retrieve author information. Starting for my instance hachyderm.io. This necessitated updates to the <code>AuthorHelper</code> component to handle such scenarios.</p>
<p>The implementation involves constructing a string to sign, incorporating elements like the request target, host, and date. This string is then signed using RSA-SHA256, and the resulting signature is included in the HTTP header. This process ensures that requests are authenticated and trusted by servers requiring signed interactions.</p>
<hr />
<p><em>Disclaimer: This post also serves as a test for some of these recent changes. LOL so if you see it like weird is because of that</em></p>
<hr />
<p><em>For a comprehensive guide on implementing ActivityPub in static sites, refer to my earlier series:</em></p>
<ul>
<li><a href="https://maho.dev/2024/02/a-guide-to-implement-activitypub-in-a-static-site-or-any-website/">A Guide to Implementing ActivityPub in a Static Site (or Any Website) - Part 1</a></li>
</ul>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>ActivityPub</category>
      <category>Static Sites</category>
      <category>Fediverse</category>
      <category>Web Development</category>
      <category>Content Delivery</category>
      <category>Signed Requests</category>
      <category>RSA-SHA256</category>
      <category>Templates</category>
    </item>
    <item>
      <title>When AI Spits Your Own Shitty Code Back at You</title>
      <link>https://maho.dev/2025/03/when-ai-spits-your-own-shitty-code-back-at-you/</link>
      <pubDate>Tue, 11 Mar 2025 01:24:39 +0000</pubDate>
      <guid>https://maho.dev/2025/03/when-ai-spits-your-own-shitty-code-back-at-you/</guid>
      <description>A rollercoaster of pride, panic, and AI gasliting I code to relax. Some people do yoga, some meditate—I open VS Code at 11 PM like a gremlin-raccoon. Don&apos;t judg...</description>
      <content:encoded><![CDATA[<h2 id="a-rollercoaster-of-pride-panic-and-ai-gasliting">A rollercoaster of pride, panic, and AI gasliting</h2>
<p>I code to relax. Some people do yoga, some meditate—I open VS Code at 11 PM like a gremlin-raccoon. Don't judge.</p>
<p>I work full-time at Microsoft, and between family time and my other hobbies, I somehow keep starting new side projects. I've got <a href="www.vocalcat.com">VocalCat</a>, an AI interviewer for <a href="www.somos.tech">SOMOS.tech</a>, some fediverse experiments, and a Mastodon fork where I play with identity and static blog integrations. And last night, against all common sense, I started another one.</p>
<p>I couldn't help it. The itch was too strong. I spent hours before sleeping, just thinking about the implementation, and suddenly, I blacked out and woke up at 1 AM with a new repo and a half-baked ActivityPub prototype in .NET.</p>
<p>Now, if you know the fediverse, you know that Microsoft isn't exactly its favorite company, and for a reason. If you are not in the fediverse, you should know that Microsoft is one of the less &quot;appreciated&quot; companies there, and with reason. The Microsoft of 20 years ago is not the same as today. I am proud to work at the Microsoft of today, but I don't blame someone who has reservations about it. I would probably have them as well if I hadn't seen things from the inside. Still, using .NET for an ActivityPub project is basically me speedrunning how fast I can get ignored by the community. Whatever. I was on a mission.</p>
<p>So, I did what any reasonable person would do: I called in the AI reinforcements. Copilot, Claude, ChatGPT, GitHub Copilot, you name it—I use them all like a chaotic symphony of artificial intelligence. Planning travel, reformatting documents, fixing grammar, mainly coding. Sometimes I get better answers by just asking directly to ChatGPT, sometimes I use the inline GitHub Copilot, sometimes the Cursor IDE. And if you're about to tell me “Did you try X prompt?” in the comments—don't. I know what I'm doing (most of the time).</p>
<p>Anyway, I asked Copilot for some help cleaning up my prototype, and the response made me look like a complete clown.</p>
<p><strong>It regurgitated my own shitty code.</strong></p>
<p>I mean, not just similar code—<em>my</em> code. My messy magic strings, my lazy namespace choices, my shortcuts, my missing activitypub implementations. I hadn't even used Copilot for the first implementation. But it was open-sourced, got slurped into some LLM training set, and now it was getting thrown back at me like a loogie that missed its target.</p>
<p>So now, I'm sitting here with two conflicting emotions:</p>
<ol>
<li><strong>Pride</strong> – I made it. My code is out there, influencing AI. I am immortal (in the most insignificant, background-noise kind of way).</li>
<li><strong>Existential crisis</strong> – If the AI is just regurgitating my own mess, how the hell am I supposed to trust it to know better than me?!</li>
</ol>
<p>Look, I don't blindly trust AI, but I do check its suggestions. And I hate to admit it, but often it <em>is</em> right. Many times, I've grumbled and copy-pasted its fix because it was better than my first draft. But sometimes? Sometimes it's just like getting code reviews from an overly confident junior engineer who barely skimmed the requirements.</p>
<p>Moral of the story? AI copilots are cool, but they are not wizards. They're just fancy autocomplete with an attitude. The Agile Manifesto says “Individuals and interactions over processes and tools,” and damn, was that reinforced for me today. AI is a tool.</p>
<p>So, yeah. Trust your instincts, use AI wisely, and if your own garbage code comes back to haunt you—well, congratulations, you've contributed to the collective digital chaos.</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>AI</category>
      <category>coding</category>
      <category>GitHub Copilot</category>
      <category>software development</category>
      <category>side projects</category>
      <category>Microsoft</category>
      <category>ActivityPub</category>
      <category>fediverse</category>
      <category>programming</category>
      <category>technology</category>
    </item>
    <item>
      <title>Why Agile Beats Processes and why the Agile Manifesto is the GOAT document of Software Development</title>
      <link>https://maho.dev/2025/03/why-agile-beats-processes-and-why-the-agile-manifesto-is-the-goat-document-of-software-development/</link>
      <pubDate>Thu, 06 Mar 2025 01:24:39 +0000</pubDate>
      <guid>https://maho.dev/2025/03/why-agile-beats-processes-and-why-the-agile-manifesto-is-the-goat-document-of-software-development/</guid>
      <description>Processes, tools, and extensive documentation are not going to save your ass, but the Agile Manifesto may. A friend just landed a job after a long hiatus. The v...</description>
      <content:encoded><![CDATA[<h2 id="processes-tools-and-extensive-documentation-are-not-going-to-save-your-ass-but-the-agile-manifesto-may">Processes, tools, and extensive documentation are not going to save your ass, but the Agile Manifesto may.</h2>
<p>A friend just landed a job after a long hiatus. The very next day, I asked him if he'd been fired yet. I know, I'm mean, but with my close friends, this is how we communicate. Don't judge.</p>
<p>He laughed (<em>Narrator: in fact he did not</em>) and then, for the <em>bazillionth</em> time, told me how nice it must be working at Microsoft. &quot;You guys have all the processes, all the resources. Everything just works!&quot;</p>
<p>Bro.</p>
<p>I spent almost 15 years working in &quot;the third world&quot; before Microsoft, and let me tell you—some of the development processes we had <em>back then</em> were better than what I see <em>today</em> at a trillion-dollar company. Money and expertise don't automatically solve scaling challenges. It's easier to implement great dynamics in a 20-person startup than in a massive corporation with legacy systems, layers of approvals, and a decision-making process that sometimes feels like a UN summit.</p>
<p>Now, don't get me wrong, Microsoft is an incredible company. But the idea that <em>every</em> team runs like a well-oiled machine? Yeah, no. The reality is more like a massive airport: some gates are efficient, others make you question the meaning of life while waiting for baggage claim.</p>
<p>And speaking of processes, my friend then went off about how his company manages risks. Apparently, they… don't. At all. (Yikes.)</p>
<p>But here's the thing—I don't believe risk management, planning, estimation, or <em>any</em> software development process should be rigidly standardized. A napkin with risks scribbled in coffee stains? Sometimes that's all you need. The right process is the one that fits the context. My friend should have just <em>observed</em> for a few weeks before making recommendations. Instead, he came in on <em>day two</em> ready to change the world. Rookie mistake.</p>
<p>That's when I thought, &quot;Man, I need to write about Agile again.&quot;</p>
<p>Because Agile—real Agile, <em>not the bloated, over-processed, meeting-heavy nonsense some companies call Agile</em>—is one of the greatest things to happen in software development. Maybe even <em>the</em> greatest. It wasn't &quot;created&quot; so much as it was documented, an aspirational state that too many teams have somehow managed to turn into a bureaucratic nightmare.</p>
<p>But first, let's be clear: <strong>SCRUM IS NOT AGILE.</strong> Agile is not a process or a set of processes, it is a mindset, a philosophy, a set of values and principles that guide how we work and interact.</p>
<p>So, let's revisit the core Agile values, shall we?</p>
<ol>
<li><strong>Individuals and interactions over processes and tools.</strong></li>
<li><strong>Working software over comprehensive documentation.</strong></li>
<li><strong>Customer collaboration over contract negotiation.</strong></li>
<li><strong>Responding to change over following a plan.</strong></li>
</ol>
<p>Think about it—what really matters? Having a process for communication, or <em>actual</em> communication? Working software, or a beautiful 200-page requirements doc that nobody reads? A solid plan for managing risks, or actually mitigating risks in real-time?</p>
<p>It's like a restaurant. You don't care how the kitchen is managed, what inventory system they use, or whether their supply chain is AI-powered—you just want good food, served on time, and <em>not</em> poisoned. Michelin-star restaurants have meticulous processes, sure, but at the end of the day, the <em>result</em> is what matters.</p>
<p>So next time you're drowning in meetings about processes, ask yourself:</p>
<ul>
<li>Is it working?</li>
<li>If not, is <em>more</em> process really the solution?</li>
<li>Or is it actually about the people, the team dynamics, and the way we collaborate?</li>
</ul>
<p>Think about it. And stay tuned—I'll be writing more into these Agile values soon.</p>
<p>Take care. And share this with only friend in need.</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>Agile</category>
      <category>Agile Manifesto</category>
      <category>Software Development</category>
      <category>Processes</category>
      <category>Tools</category>
      <category>Individuals</category>
      <category>Interactions</category>
      <category>Working Software</category>
      <category>Documentation</category>
      <category>Customer Collaboration</category>
      <category>Contract Negotiation</category>
      <category>Responding to Change</category>
      <category>Planning</category>
      <category>Risk Management</category>
      <category>Team Dynamics</category>
    </item>
    <item>
      <title>Open Letter to Nonprofits and Public Organizations: Reconsidering Our Social Media Platforms</title>
      <link>https://maho.dev/2025/03/open-letter-to-nonprofits-and-public-organizations-reconsidering-our-social-media-platforms/</link>
      <pubDate>Sun, 02 Mar 2025 01:24:39 +0000</pubDate>
      <guid>https://maho.dev/2025/03/open-letter-to-nonprofits-and-public-organizations-reconsidering-our-social-media-platforms/</guid>
      <description>Amplify Your Organization&apos;s Voice: Embrace Radical Transparency and Authentic Connections Dear Leaders of Nonprofits and Public Organizations, In our digital ag...</description>
      <content:encoded><![CDATA[<h1 id="amplify-your-organizations-voice-embrace-radical-transparency-and-authentic-connections">Amplify Your Organization's Voice: Embrace Radical Transparency and Authentic Connections</h1>
<p>Dear Leaders of Nonprofits and Public Organizations,</p>
<p>In our digital age, social media platforms have become indispensable tools for communication, outreach, and community engagement. However, the landscape of these platforms is evolving, it’s crucial for organizations like yours to rethink where and how you engage online.</p>
<h2 id="the-challenge-with-centralized-platforms">The Challenge with Centralized Platforms</h2>
<p>Centralized social media platforms, such as X (formerly Twitter), operate under profit-driven models that can influence the dissemination of information. Studies have shown that Twitter’s algorithms tend to amplify content from right-wing politicians and news outlets more than from the left. Regardless of political orientation, algorithmic biases driven by profit interests are incompatible with the public interest.</p>
<p>This inherent bias can inadvertently skew public discourse, affecting the visibility of crucial information.</p>
<p>For public entities — such as fire departments, police departments, universities, and other essential services — this presents a significant concern. Relying on platforms where private entities control content algorithms poses risks, as these algorithms may inadvertently delay or obscure urgent messages. For instance, during recent emergencies, platforms like TikTok, Instagram, and X/Twitter were inundated with disaster-related content, yet they often failed to provide timely, practical assistance to those affected. Even worse, the algorithms can also amplify misinformation, further complicating efforts to deliver accurate, life-saving information.</p>
<p>This underscores the challenges of relying on algorithm-driven platforms for emergency communications.</p>
<p>I don’t like that public entities are giving power to private individuals, but I am even more troubled that these individuals have used this power indiscriminately for self-benefit. We gave them too much power, and now it is time to demand that such organizations embrace open standards and open platforms. In an ideal world, public entities would self-host services for video, microblogging, alerts, newsletters, and more — just as they self-host or contract email and web services. I am okay with platforms like YouTube offering a white-label platform, free from algorithm-driven motivators that work against public self-interest. But for organizations like Amtrak or NOAA to rely on X/Twitter for alerts or YouTube for videos is akin to using a @gmail email address or hosting their website on Geocities. We deserve better.</p>
<h2 id="the-public-discourse-in-the-control-of-individuals">The Public Discourse in the Control of Individuals</h2>
<p>The centralized control of influential digital platforms has empowered individuals like Elon Musk to significantly influence political discourse. Independently if you agree or not with views of the individuals, their dual role as a senior adviser to Government and owner of X has raised concerns about potential conflicts of interest, especially regarding decisions like influencing federal contracts to favor his business interests.</p>
<p>This convergence of media ownership and political advisory roles exemplifies how centralized control over digital platforms can enable private individuals to shape public discourse and policy decisions, often aligning with their personal or corporate interests.</p>
<h2 id="the-decentralized-alternative">The Decentralized Alternative</h2>
<p>Decentralized platforms, such as Mastodon, offer a compelling alternative. By distributing control among users rather than centralizing it within a single entity, these platforms enhance transparency and reduce the likelihood of algorithmic manipulation serving specific agendas. This structure ensures that communications remain unbiased, and engagement with communities is authentic and free from corporate interference.</p>
<p>Moreover, decentralized platforms provide greater ownership over personal data and improved control over user-generated content. Nonprofits, for example, can migrate to different servers or even run their own Mastodon instance, just as they manage their websites and email services. This gives them the ability to communicate directly with their audience, without relying on third-party platforms that may prioritize profit over public interest or restrict their reach.</p>
<h2 id="a-word-on-emerging-platforms">A Word on Emerging Platforms</h2>
<p>While new platforms like Bluesky present themselves as decentralized social networks, it is essential to approach them with caution. Despite their decentralized architecture, they remain for-profit entities, which could influence their operational decisions and priorities. As such, the potential for profit motives to impact content visibility and platform policies cannot be entirely dismissed.</p>
<p>What is next?</p>
<p>Given these considerations, we urge nonprofits and public organizations to reevaluate their social media strategies critically. Transitioning away from platforms that enable oligarchic control and corporate interests over the public square is a step towards preserving the integrity and efficacy of your communications.</p>
<p>We understand that this process is not easy or simple. That’s why we encourage nonprofits to adopt Mastodon alongside their existing social networks while migrating or condensing their social media presence. It’s important to start building that presence now so your audience knows where to find reliable, unfiltered information about your organization — without interference or misinformation.</p>
<p>Embracing decentralized platforms like Mastodon can empower your organization to maintain direct, unbiased, and effective engagement with the communities you serve.</p>
<p>By making this shift, you not only safeguard your organization’s mission but also contribute to a more equitable and transparent digital ecosystem.</p>
<p>There is a whole community ready to help you, and I am eager to assist your nonprofit or public organization in migrating to a more sustainable and independent platform.</p>
<p>With commitment to change,</p>
<p>and unwavering support,</p>
<p>Sincerely,</p>
<p>Maho Pacheco</p>
<h2 id="where-to-start">Where to start?</h2>
<ul>
<li><a href="https://joinmastodon.org/">Join Mastodon</a>.</li>
<li><a href="https://www.ebu.ch/news/2024/03/public-broadcasters-of-europe-lets-all-join-mastadon">Public Broadcasters of Europe, Let’s All Start Microblogging</a>.</li>
<li><a href="https://www.vocalcat.com/mastodon-resources">Mastodon Resources for non-profits and public organizations</a>.</li>
</ul>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>Fediverse</category>
      <category>Decentralization</category>
      <category>PublicInterest</category>
      <category>OpenStandards</category>
      <category>SelfHosting</category>
      <category>NonprofitTech</category>
      <category>DigitalSovereignty</category>
      <category>EthicalTech</category>
      <category>AltSocialMedia</category>
      <category>OpenPlatforms</category>
      <category>PublicCommunications</category>
      <category>Mastodon</category>
      <category>IndieWeb</category>
      <category>TechForGood</category>
      <category>DigitalFreedom</category>
      <category>AlgorithmicBias</category>
      <category>PlatformCoop</category>
      <category>Federation</category>
      <category>FediTips</category>
      <category>PublicSectorTech</category>
    </item>
    <item>
      <title>How to Multistream Using Azure, Azure VM, MonaServer 2, and FFmpeg with OBS Studio</title>
      <link>https://maho.dev/2025/02/how-to-multistream-using-azure-azure-vm-monaserver-2-and-ffmpeg-with-obs-studio/</link>
      <pubDate>Fri, 21 Feb 2025 01:24:39 +0000</pubDate>
      <guid>https://maho.dev/2025/02/how-to-multistream-using-azure-azure-vm-monaserver-2-and-ffmpeg-with-obs-studio/</guid>
      <description>In this guide, we will set up a multistreaming workflow using Azure, an Azure VM, MonaServer 2, and FFmpeg to broadcast to multiple platforms like LinkedIn Live...</description>
      <content:encoded><![CDATA[<p>In this guide, we will set up a multistreaming workflow using Azure, an Azure VM, MonaServer 2, and FFmpeg to broadcast to multiple platforms like LinkedIn Live and YouTube Live. We'll use OBS Studio as the main streaming software. Let’s get started.</p>
<h2 id="motivation">Motivation</h2>
<p>Ever looked at those $15–$20/month streaming services and thought, &quot;I have Azure credits—why am I paying for this?&quot; Same here. Plus, my friend <a href="https://www.lqdev.me/wiki/owncast">Luis Quintanilla introduced me to Owncast</a> , which I plan to use soon. So, the question became: Can I achieve multistreaming using open-source tools? And if so, how do I stream to Owncast and other platforms at the same time?</p>
<h2 id="overview">Overview</h2>
<p>The multistreaming setup works as follows:</p>
<ol>
<li>OBS Studio streams to a relay server running on an Azure VM.</li>
<li>MonaServer 2 acts as an RTMP server, receiving the stream.</li>
<li>FFmpeg restreams the RTMP feed to LinkedIn Live and YouTube Live simultaneously.</li>
</ol>
<p>This approach minimizes bandwidth usage on your local machine (which is a pain in my home with so many devices and one kid) while ensuring a stable stream to multiple platforms.</p>
<p><img src="/img/monaserver.png" alt="Overview" /></p>
<h2 id="step-1-setting-up-an-azure-vm">Step 1: Setting Up an Azure VM</h2>
<p>First, provision a virtual machine on Azure:</p>
<ul>
<li><strong>Choose an Ubuntu-based VM (e.g., Standard B2s or higher).</strong></li>
<li><strong>Ensure inbound ports for SSH (22) and RTMP (1935) are open.</strong></li>
<li><strong>Install dependencies:</strong></li>
</ul>
<pre><code class="language-bash">sudo apt update &amp;&amp; sudo apt upgrade -y
sudo apt install -y ffmpeg git
</code></pre>
<h2 id="step-2-installing-monaserver-2">Step 2: Installing MonaServer 2</h2>
<p>MonaServer 2 is a lightweight RTMP server that will handle incoming streams from OBS Studio.</p>
<p>I followed the instructions on <a href="https://github.com/MonaSolutions/MonaServer2">https://github.com/MonaSolutions/MonaServer2</a> which involved
installing gcc++, libss-dev (<code>sudo apt get install gcc++ libssl-dev</code>) and LuaJIT manually.</p>
<p>With LuaJit I did not had an issue to compile it manually. With MonaServer 2 however, I modified:</p>
<p><code>MonaBase/include/Mona/Mona.h</code> and added <code>#include &lt;stdexcept&gt;</code> after the includes (<code>#include &lt;cmath&gt;</code>)
and then it compiled successfully. Then I manually copied <code>MonaBase/lib/libMonaBase.so</code> into <code>MonaServer</code>.</p>
<h3 id="run-monaserver">Run MonaServer</h3>
<pre><code class="language-bash">./MonaServer &amp;
</code></pre>
<p>By default, MonaServer listens for RTMP streams on <code>rtmp://&lt;your-vm-ip&gt;/live</code>.</p>
<h2 id="step-3-configuring-obs-studio">Step 3: Configuring OBS Studio</h2>
<ol>
<li><strong>Open OBS Studio</strong> and go to <strong>Settings &gt; Stream</strong>.</li>
<li><strong>Select ‘Custom’ as the streaming service.</strong></li>
<li><strong>Set the RTMP server to:</strong>
<pre><code>rtmp://&lt;your-vm-ip&gt;/live
</code></pre>
</li>
<li><strong>No need to set the Stream Key</strong>.</li>
<li>Click <strong>Apply</strong> and <strong>Start Streaming</strong>.</li>
</ol>
<p>At this point, OBS will send the stream to your Azure VM running MonaServer.</p>
<p>You could test this by using VLC and opening <code>rtmp://&lt;your-vm-ip&gt;/live</code>.</p>
<h2 id="step-4-using-ffmpeg-to-restream">Step 4: Using FFmpeg to Restream</h2>
<p>Now that MonaServer is receiving the stream, we use FFmpeg to send it to LinkedIn Live and YouTube Live.</p>
<pre><code class="language-bash">ffmpeg -i rtmp://localhost/live/my_stream \
    -c:v copy -c:a aac -b:a 128k -f flv &quot;rtmp://a.rtmp.youtube.com/live2/YOUR_YOUTUBE_STREAM_KEY&quot; \
    -c:v copy -c:a aac -b:a 128k -f flv &quot;rtmp://live.linkedin.com/media-server/YOUR_LINKEDIN_STREAM_KEY&quot;
</code></pre>
<p>This command:</p>
<ul>
<li>Takes the RTMP input from MonaServer.</li>
<li>Copies the video codec (avoiding re-encoding).</li>
<li>Sends the output to YouTube and LinkedIn.</li>
</ul>
<p>There are <a href="https://obsproject.com/forum/resources/obs-studio-stream-to-multiple-platforms-or-channels-at-once.932/">other options described here</a>.</p>
<h2 id="step-5-automating-ffmpeg">Step 5: Automating FFmpeg</h2>
<p>For a more robust setup, create a script to keep FFmpeg running:</p>
<pre><code class="language-bash">echo '#!/bin/bash
while true; do
  ffmpeg -i rtmp://localhost/live/my_stream \
      -c:v copy -c:a aac -b:a 128k -f flv &quot;rtmp://a.rtmp.youtube.com/live2/YOUR_YOUTUBE_STREAM_KEY&quot; \
      -c:v copy -c:a aac -b:a 128k -f flv &quot;rtmp://live.linkedin.com/media-server/YOUR_LINKEDIN_STREAM_KEY&quot;
  sleep 5
done' &gt; start-stream.sh

chmod +x start-stream.sh
nohup ./start-stream.sh &amp;
</code></pre>
<p>Or you can even put your streams in a txt file.</p>
<p>This ensures FFmpeg restarts automatically if it stops.</p>
<h2 id="conclusion">Conclusion</h2>
<p><img src="/img/multistream.png" alt="Overview" /></p>
<p>With this setup, you can multistream from OBS Studio to LinkedIn Live and YouTube Live using Azure, an Azure VM, MonaServer 2, and FFmpeg. This method optimizes bandwidth and ensures a stable stream for multiple platforms.</p>
<p>If you are interested in what I stream checkout <a href="https://www.youtube.com/@TheRaccoonBytes">The Raccoon Bytes</a>, otherwise
if you have questions or run into issues, let me know in the comments!</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>Azure</category>
      <category>Streaming</category>
      <category>Live Streaming</category>
      <category>OBS Studio</category>
      <category>LinkedIn Live</category>
      <category>YouTube Live</category>
      <category>FFmpeg</category>
      <category>MonaServer</category>
    </item>
    <item>
      <title>A Guide to Implementing ActivityPub in a Static Site (or Any Website) - Part 8</title>
      <link>https://maho.dev/2025/01/a-guide-to-implementing-activitypub-in-a-static-site-or-any-website-part-8/</link>
      <pubDate>Tue, 21 Jan 2025 01:24:39 +0000</pubDate>
      <guid>https://maho.dev/2025/01/a-guide-to-implementing-activitypub-in-a-static-site-or-any-website-part-8/</guid>
      <description>Find the index and earlier parts of this series here . We&apos;re almost there! Thanks for sticking with me on this journey. In this final part, we&apos;ll integrate repl...</description>
      <content:encoded><![CDATA[<blockquote>
<p>Find the <a href="https://maho.dev/2024/02/a-guide-to-implement-activitypub-in-a-static-site-or-any-website/">index and earlier parts of this series here</a>.</p>
</blockquote>
<p>We're almost there! Thanks for sticking with me on this journey. In this final part, we'll integrate replies and comments into your static website.</p>
<h2 id="motivation">Motivation</h2>
<p>Integrating replies is quite similar to how we handled subscriptions. Since our posts now exist in the Fediverse, we want to treat them as first-class citizens. This means replies to our posts should appear on our site.</p>
<p>Think of our site as an ActivityPub implementation (with some limitations and that it's not fully compliant). The key tasks here are:</p>
<ol>
<li>Handling replies</li>
<li>Displaying replies</li>
</ol>
<p>Fortunately, we've already set up an <code>Inbox</code> endpoint and handled one type of message: <code>Follow</code>. Now we'll extend this to support <code>Create</code> activities (notes).</p>
<h2 id="overview">Overview</h2>
<p>The <code>Inbox</code> handles <code>POST</code> HTTP requests for various actions. These requests may or may not be signed, and you can choose how to respond. There's no strict minimum for an ActivityPub implementation, as seen with Threads' phased federation rollout. Building on Part 6, we'll now add support for replies.</p>
<h3 id="replies-the-flow">Replies: The Flow</h3>
<p>Here's a high-level view of the process (we'll break it down step-by-step):</p>
<p>{{&lt; mermaid &gt;}}<br />
sequenceDiagram<br />
participant Commenter instance
participant Inbox<br />
participant Table-Storage<br />
participant Static-Storage<br />
Commenter instance-&gt;&gt;Inbox: Create activity note (reply) request
Inbox-&gt;&gt;Commenter instance: 200 OK<br />
Inbox-&gt;&gt;Table-Storage: Save reply URI/ID<br />
Inbox-&gt;&gt;Static-Storage: Generate replies collection<br />
{{&lt; /mermaid &gt;}}</p>
<p>In Part 4, we added a <code>replies</code> endpoint for each post, like this:</p>
<pre><code class="language-json">&quot;replies&quot;: {  
  &quot;id&quot;: &quot;https://maho.dev/socialweb/replies/a9e885b19fe0a2aceaf7696eb6d4b646&quot;,  
  &quot;type&quot;: &quot;Collection&quot;,  
  &quot;first&quot;: {  
    &quot;type&quot;: &quot;CollectionPage&quot;,  
    &quot;next&quot;: &quot;https://maho.dev/socialweb/replies/a9e885b19fe0a2aceaf7696eb6d4b646?page=true&quot;,  
    &quot;partOf&quot;: &quot;https://maho.dev/socialweb/replies/a9e885b19fe0a2aceaf7696eb6d4b646&quot;,  
    &quot;items&quot;: []  
  }  
}
</code></pre>
<p>The endpoint is predictable because we know the internal ID of the note (<code>a9e885b19fe0a2aceaf7696eb6d4b646</code>). Even if the URL doesn't exist when the post is created, it's still referenced in the note.</p>
<blockquote>
<p><em>Note:</em> This collection isn't truly paginated—it just mimics Mastodon's structure.</p>
</blockquote>
<p>In the last step, we generate the replies collection. It looks like this:</p>
<pre><code class="language-json">{
  &quot;@context&quot;: &quot;https://www.w3.org/ns/activitystreams&quot;,
  &quot;id&quot;: &quot;https://maho.dev/socialweb/replies/a9e885b19fe0a2aceaf7696eb6d4b646?page=true&quot;,
  &quot;partOf&quot;: &quot;https://maho.dev/socialweb/replies/a9e885b19fe0a2aceaf7696eb6d4b646&quot;,
  &quot;type&quot;: &quot;CollectionPage&quot;,
  &quot;items&quot;: [
    &quot;https://hachyderm.io/users/mapache/statuses/113868758629132410&quot;
  ]
}
</code></pre>
<p>The <code>items</code> array holds URIs for replies. To avoid managing deleted or modified statuses, we only store identifiers and fetch the data in real-time.</p>
<hr />
<h3 id="net-implementation">.NET Implementation</h3>
<p>Find the implementation in my repo: <a href="https://github.com/mahomedalid/almost-static-activitypub">Almost Static ActivityPub</a>. Check out these key files:</p>
<ul>
<li><a href="https://github.com/mahomedalid/almost-static-activitypub/blob/main/src/ActivityPubDotNet.Core/Storage/RepliesGenerator.cs"><code>RepliesGenerator.cs</code></a></li>
<li><a href="https://github.com/mahomedalid/almost-static-activitypub/blob/main/src/ActivityPubFunctions/RepliesService.cs"><code>RepliesService.cs</code></a></li>
</ul>
<p>In <code>RepliesService.cs</code>, the <code>Inbox</code> only processes <code>Create</code> messages originating from our domain:</p>
<pre><code class="language-csharp">if (!objectNote!.InReplyTo?.StartsWith(Domain) ?? true) {
    return; // Ignore messages from other domains
}
</code></pre>
<p>This filters out irrelevant messages from federated instances. You may want to add other filters?</p>
<h2 id="displaying-replies">Displaying Replies</h2>
<p>Now that replies are stored, how do we show them on the site? Inspired by other implementations, I fetch comments based on the Mastodon post URL, which in our case is our ActivityPub post URL.</p>
<p>Here's the logic:</p>
<ol>
<li>Fetch the replies URL (<code>https://maho.dev/socialweb/replies/bcc93f1e77a9eaa4277b430815352ddd</code>), appending a timestamp to bypass caching.
<ul>
<li>If 404, no replies exist yet.</li>
</ul>
</li>
<li>For each reply, fetch its data (<code>application/activity+json</code>).</li>
<li>Parse the comment and display it.</li>
</ol>
<p>This is the JavaScript powering the &quot;Load Comments&quot; button:</p>
<pre><code class="language-javascript">function loadComments() {
    let commentsWrapper = document.getElementById(&quot;comments-wrapper&quot;);
    const timestamp = Date.now();

    document.getElementById(&quot;load-comment&quot;).innerText = &quot;Loading&quot;;
    fetch(`https://maho.dev/socialweb/replies/bcc93f1e77a9eaa4277b430815352ddd?timestamp=${timestamp}`)
        .then(response =&gt; {
            if (response.ok) return response.json();
            if (response.status === 404) {
                commentsWrapper.innerHTML = &quot;&lt;p&gt;No comments found&lt;/p&gt;&quot;;
                return Promise.reject(&quot;No comments found&quot;);
            }
            commentsWrapper.innerHTML = &quot;&lt;p&gt;Error fetching comments.&lt;/p&gt;&quot;;
            return Promise.reject(&quot;Error fetching comments&quot;);
        })
        .then(data =&gt; Promise.all(
            data.items.map(replyId =&gt; fetch(replyId, {
                headers: { 'Content-Type': 'application/activity+json', 'Accept': 'application/activity+json' }
            }).then(response =&gt; response.ok ? response.json() : Promise.reject(&quot;Error fetching reply&quot;)))
        ))
        .then(responses =&gt; {
            responses.sort((a, b) =&gt; new Date(a.created_at) - new Date(b.created_at));
            commentsWrapper.innerHTML = responses.map(status =&gt; `&lt;p&gt;${status.content}&lt;/p&gt;`).join(&quot;&quot;);
        })
        .catch(console.error);
}
document.getElementById(&quot;load-comment&quot;).addEventListener(&quot;click&quot;, loadComments);
</code></pre>
<p>As you can see, nothing out of the world. You can find the <a href="https://maho.dev/js/mastodon-comments.js">full implementation here</a> or just peeking to my page source code.</p>
<h2 id="final-note">Final note</h2>
<p>With this final piece in place, your static site now integrates seamlessly with the Fediverse by handling replies and comments. By leveraging ActivityPub, you've turned a static site into a social participant, capable of engaging with decentralized networks like Mastodon. This implementation not only enhances the interactivity of your site but also keeps it lightweight and maintainable.</p>
<p>If you have a static site on the Fediverse, feel free to share it—I'd love to check it out and follow your journey! Thank you for reading this series!</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>Fediverse</category>
      <category>ActivityPub</category>
      <category>Static Sites</category>
      <category>Hugo</category>
      <category>Azure</category>
      <category>Mastodon</category>
      <category>Web Development</category>
      <category>Social Web</category>
      <category>WebFinger</category>
      <category>HTTP</category>
      <category>Azure</category>
      <category>AzureFunctions</category>
    </item>
    <item>
      <title>The Power of Representation</title>
      <link>https://maho.dev/2024/12/the-power-of-representation/</link>
      <pubDate>Mon, 23 Dec 2024 00:33:08 -0700</pubDate>
      <guid>https://maho.dev/2024/12/the-power-of-representation/</guid>
      <description>Yeah, I know the last 3 chapters are titled &amp;quot;The Power of...&amp;quot; but let me tell you, I like it, and it&apos;s my blog. When I was a kid, I always knew I want...</description>
      <content:encoded><![CDATA[<blockquote>
<p>Yeah, I know the last 3 chapters are titled &quot;The Power of...&quot; but let me tell you, I like it, and it's my blog.</p>
</blockquote>
<p>When I was a kid, I always knew I wanted to make something different for myself. Different from what? That was the question.</p>
<p>I didn't limit my imagination to a subset of possibilities. Everything was on the table: President of Mexico one day? Archaeologist discovering new dinosaurs? Sure, sounds fun. Composer and musician? Maybe, just maybe. But each possibility carried a hint of reality, shaped by moments like watching my mom count coins in desperation to buy a kilo of tortillas, or by the fact that everyone around me was low-income, grappling with the unjust lack of opportunities faced by kids like me—dark brown-skinned children in a deeply classist country.</p>
<p>As I grew older, the table of possibilities began to empty, one dream dropping off after another. My passion for music wasn't enough to get me into a conservatory; after all, we couldn't afford an instrument, let alone lessons (and to be honest I wasn't good enough). Becoming an archaeologist required moving to another city, an impossibility with no money to fund it. Instead, a narrow but promising path emerged: my hobby of tinkering with computers could actually become a career. By middle school, I was already coding in COBOL, Basic, and Pascal. An old computer lent by an uncle and code written in paper notebooks allowed a kid to teach himself the craft.</p>
<p>Despite the lack of material wealth, my childhood was a happy one. There was never a shortage of love and support—even if my father's way of expressing it was limited—and my mother's care was boundless. My parents made extraordinary efforts to provide for me. My mom worked up until the final weeks of a pregnancy at a private school to secure a highly discounted scholarship. My dad worked endless overtime. Thanks to them, I attended a school where I didn't fit in. Kids made fun of my backpack and school supplies. Birthday parties were held in houses so grand I couldn't navigate the etiquette. These houses were in neighborhoods where public transportation was absent, so I'd walk blocks and blocks to get there. Meanwhile, my afternoons were spent playing soccer in the dusty streets of our neighborhood, surrounded by half-built houses, some made of cardboard. My neighbors were working-class families: trash pickers, chicken sellers, construction workers, carpenters.</p>
<p><img src="/img/my_hood.png" alt="My hood in Tepic, Nayarit" />
<em>A Google Street View of My Neighborhood 2014</em></p>
<p>I was stuck between two worlds. At school, I struggled to find my place among wealthier kids who, despite everything, became my friends. Back home, my neighborhood friends often saw me as cocky or condescending. What I didn't realize then was that both groups were longing for what the other had.</p>
<p>Time passed, and this duality became part of my personality. Life went on, with me enjoying fancy dinners I couldn't afford and camping in places nobody else would. I was still happy, still grateful, still hopeful to make a name for myself. But the &quot;industry&quot; I aimed for remained a vague idea. I bounced between sysadmin, graphical designer, IT support, and software developer roles. Meanwhile, many of my childhood friends stayed the same, some never finishing middle school due to crime, drugs, unexpected pregnancies, or the necessity to work.</p>
<p>I vividly remember reading <em>The Hacker Manifesto</em>, trying to explain its allure to a neighbor who looked at me with confusion—or maybe disbelief. The idea of teaching transistors and semiconductors what to do, transforming electricity into something tangible and valuable, amazed me. I remember how my dad's job gave us a heavily subsidized but still expensive computer and DSL connection. I discovered <em>Dragon Ball Z</em> spoilers online (with my broken English) and shared them with my block friends, who mocked me for saying such &quot;crazy&quot; things would happen. Two years later, when those episodes finally aired on TV, I proved I was right (<em>who is laughing now mxxfers!!</em>)</p>
<p>No one around me was like me. The limited career scope for IT professionals was clear: be like my dad, an internet technician; like my uncle, a networking guy at Mexico's largest ISP; or like the people who occasionally hired me, programming systems for local businesses or teaching at a local university. I turned down the possibility of a stable job at my father's company. I wanted more, or at least something different.</p>
<p>Still living at my parents' house, I decided to make my first Amazon order in 2005: the <em>Mono.NET</em> book. It took months to arrive in Mexico, and when it did, I devoured it, treasured it. Immersed in open-source projects, install-linux days and the GNU Manifesto, I discovered GNOME. At the time, GNOME and KDE were the two dominant Linux desktop environments. When I learned that GNOME's creator was Miguel de Icaza, I knew immediately I was Team GNOME.</p>
<p><img src="/img/mono_dot_net_order.png" alt="An Amazon MONO.net book order from 2004" />
<em>My first Amazon order, just paying in USD and shipping something to Mexico was extraordinary</em></p>
<p>Miguel de Icaza, the creator of GNOME and later Mono.NET, came from a vastly different background than mine—different skin color, different opportunities: a prestigious university, and a city brimming with possibilities. Yet, seeing a Mexican thriving in the &quot;big leagues&quot; made a crack in the wall of impossibility that surrounded me. I didn’t understand it fully at the time, but representation isn’t just inspiring—it’s transformative.</p>
<p>Miguel went on to create Xamarin and eventually joined (or was acquired by) Microsoft before moving on. Back then, I was busy creating clones of famous applications using GTK#. After spending a few years attempting to build an open-source community in my hometown and unsuccessfully applying for Google summer internships, I decided to move to a larger city—and the rest is a story that continues in <a href="https://mahopacheco.substack.com/p/tales-of-tech-and-failure-unplugged">other posts</a> and other time.</p>
<p>However, I wasn't aware of all this until a few days ago, when a flood of memories came crashing into my brain.</p>
<p>Last week, I attended an ugly sweater event at <a href="https://somos.tech">SOMOS.tech</a>. <a href="https://www.linkedin.com/in/iamjoeycruz/">Joey Cruz</a> gave me a branded sweater, and when I got home, my kid put it on, making funny faces. That moment inspired this post. That moment nearly brought tears to my eyes, as they threaten to now while I write. My child casually says he wants to work at Microsoft, as if it's something normal and achievable. For him, it might be. He doesn't know the struggle of not seeing anyone like you on the path you're trying to forge.</p>
<p><img src="/img/kiddo_with_somostech.png" alt="My kid wearing the SOMOS.tech sweater proudly" />
<em>My kid stealing my hoodie</em></p>
<p>What I didn't realize was the impact I'm having on others. Returning to LinkedIn, I was surprised by messages from old friends expressing pride in my journey. They were proud—proud!—to see someone from their city, their university, their circle of friends, achieving what I have. (But what have I achieved anyway?) I don’t feel special; I don’t feel I’ve achieved anything extraordinary. I’m just here coding as I was 20 years ago. I believe anyone can do what I do—I’ve just been lucky with the opportunities that came my way. That’s not to say I don’t feel I’ve earned my place, but I know others might have made even more of the same chances.</p>
<p>Every couple of years, I visit my old neighborhood. Some childhood friends are gone—victims of the narco wars, some beheaded, others disappeared. These were people I played with for hours, for days, for years, sharing dreams and teenager ambitions (or the lack thereof). A neighbor once told me how many on the block had been rooting for me to succeed, to escape. They saw in the few of us who did that there was a chance for them too, maybe for their kids. How did I not realize it back then?</p>
<p>This is why I write, why I mentor, and why I strive to stay visible in spaces where others might need to see themselves reflected. Representation isn't just about achieving something for yourself—it's about holding the door open for the ones who come after you, showing them that the impossible can indeed become reality. And sometimes, it's about making sure that when they look up, they see a version of themselves already there, paving the way so they can achieve something extraordinary.</p>
<p><strong>Representation matters.</strong> It bridges the gap between the impossible and the attainable. It plants the seed of belief in others, even if they don't realize it at the moment, even if they don't know they're watching. Even if, like me, they can't point to someone specific but are forever grateful for the path that was unknowingly illuminated. I can't wait to see what others achieve, breaking glass ceilings I am physically impossible of dream anymore.</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
    </item>
    <item>
      <title>The Power Struggle of Social Media Engagement</title>
      <link>https://maho.dev/2024/12/the-power-struggle-of-social-media-engagement/</link>
      <pubDate>Thu, 19 Dec 2024 00:33:08 -0700</pubDate>
      <guid>https://maho.dev/2024/12/the-power-struggle-of-social-media-engagement/</guid>
      <description>The Power Struggle of Social Media Engagement Breaking Free: Reclaiming Visibility and Voice from the Algorithmic Gatekeeping. aka F**ck the algorithm. So, I ha...</description>
      <content:encoded><![CDATA[<h1 id="the-power-struggle-of-social-media-engagement">The Power Struggle of Social Media Engagement</h1>
<h2 id="breaking-free-reclaiming-visibility-and-voice-from-the-algorithmic-gatekeeping.aka-fck-the-algorithm">Breaking Free: Reclaiming Visibility and Voice from the Algorithmic Gatekeeping. aka F**ck the algorithm.</h2>
<p>So, I have an account with about 20 followers on BlueSky, and I generate some engagement on almost every post. In stark contrast, I have another account on X/Twitter that has over 2000 followers, yet 99% of the time, it feels like I'm talking to the void. My impressions hover around 30, which means roughly 30 people see my tweets—about 1.5% of my followers.</p>
<p>To increase these impressions, there's a well-known requirement: purchase the blue Twitter checkmark and the premium or pro subscription. I've seen posts from people claiming that impressions on LinkedIn drastically improve once you go premium, but since I don’t have it, I can’t really say for sure.</p>
<p>It's all quite crazy—bananas, really. On platforms like Mastodon or the Fediverse, 100% of my followers receive my posts in their feeds. This level of control that social networks hold over their algorithms feels like <strong>gatekeeping</strong>. They are essentially restricting access to their algorithms, creating <strong>walled gardens</strong> that control what we see.</p>
<p>They wield enormous power, controlling the infrastructure and influencing visibility. It’s astonishing how these algorithms dictate the reach of content on centralized platforms. Freedom of speech, not of reach. The notion that your reach—something inherently tied to the audience you've worked hard to cultivate—hinges on paying for premium features feels like a tax on digital self-expression. This gatekeeping discourages genuine engagement, creating a pay-to-play ecosystem that often sidelines smaller creators in favor of monetization over meaningful connections.</p>
<p>What are my options now? Do I really need to pay just to get my voice out there? Heck not! This is one of the reasons I am creating <a href="https://www.vocalcat.com">vocalcat</a>. It really started as a personal tool, but I am working to get others benefit of it. Small creators need the tools to battle these algorithms without compromising their unique voices or the subjects they are passionate about.</p>
<p>How many people find themselves talking about things they don't truly care about or feel pressured to create engaging memes and videos just to garner attention, while their high-effort, thought-provoking posts get minimal likes? Yes, some of this is on us as consumers, as we sometimes prioritize entertainment. But when we give control of what we want to see and what we value over to the profit-driven algorithms, we ultimately lose a crucial battle.</p>
<h3 id="tldr">TLDR;</h3>
<p>In this digital age, it's essential for creators to reclaim their power. We need to explore alternatives that allow for genuine expression and connection. Let’s not allow algorithms to define our worth.</p>
<p>Join the fediverse/mastodon, be yourself, also visit <a href="https://www.vocalcat.com">VocalCat website</a> and register for the beta, help me so we can thrive without the constraints of traditional social media algorithms. You will find your tribe. Keep going.</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
    </item>
    <item>
      <title>The Power of a Feel Good Story</title>
      <link>https://maho.dev/2024/11/the-power-of-a-feel-good-story/</link>
      <pubDate>Mon, 11 Nov 2024 20:33:08 -0700</pubDate>
      <guid>https://maho.dev/2024/11/the-power-of-a-feel-good-story/</guid>
      <description>Breaking the Cycle of Fear: How We Can Spread a Better Kind of Story A few months back, I received a free book at a Microsoft event: Infectious Generosity by Ch...</description>
      <content:encoded><![CDATA[<h3 id="breaking-the-cycle-of-fear-how-we-can-spread-a-better-kind-of-story">Breaking the Cycle of Fear: How We Can Spread a Better Kind of Story</h3>
<p>A few months back, I received a <strong>free</strong> book at a Microsoft event: <em>Infectious Generosity</em> by Chris Anderson. It sat on my bookshelf for a couple of weeks until one day, out of sheer boredom, I grabbed the shiniest cover one (a deep yellow) and started flipping through it. To my surprise, it's written by the Head of TED (I love TED!), and while much of the book is gold, this blog is about a particular section that completely resonated with a thought I've had for years. It touches on a phenomenon we've all noticed but rarely talk about: the media's relentless focus on the worst of humanity.</p>
<p>If you've read my previous piece <a href="https://maho.dev/2024/09/the-digital-umwelt.-beyond-the-noise-and-the-crap-content./">The Digital Umwelt: Beyond the Noise and Crap Content</a>, you'll know where I'm coming from.</p>
<p><img src="/img/meinfect.jpg" alt="Maho Pacheco with the book Infectious Generosity" /></p>
<p>Turn on any news channel, scroll through social media, and you'll see it: tragedy, disaster, scandal. After the 2024 U.S. presidential election, this only got worse, especially on platforms like Twitter and even BlueSky. The media is saturated with stories that tap into our deepest fears, often amplifying the most alarming aspects of any event. It's not hard to see why—these stories attract clicks, views, and engagement. Fear sells. Fear brings money. Fear even helped elect a divisive candidate. It's addictive, like watching a train wreck you can't look away from. We're hardwired for it; evolution made us that way. But here's the problem: we're now stuck in a feedback loop where negativity dominates the narrative, even though the majority of human interactions are positive. And they really are!</p>
<p>Think about it. When was the last time you saw a heartwarming story go viral on the same scale as a scandal or tragedy? Sure, you'll find examples—a dog rescue video, a touching reunion, Mr. Beast early videos—but they're exceptions, not the rule. It's not because these positive stories don't exist; it's because they're not prioritized. The media's business model thrives on our attention, and our attention is most easily captured by fear, not by warmth.</p>
<p>This realization was one of the reasons I shifted my focus in my videos. Instead of showcasing purely tech content, I started highlighting the voices of others—people who inspire me and have left a mark on my journey. <a href="https://www.youtube.com/@TheRaccoonBytes/shorts">My short videos</a> now feature snippets of advice from people I admire, with the hope of creating a &quot;good infection&quot;. I want to spread feel-good stories and amplify the voices of those who have shaped me, making space for genuine connection in a world obsessed with clicks.</p>
<p><strong>So how do we break the cycle? How do we shift the narrative from one of fear to one of hope?</strong></p>
<p>We have more power than we think. It starts with a simple act of choice—what we choose to engage with, share, and amplify. Instead of doom-scrolling through anxiety-inducing headlines, we can consciously seek out and share stories that inspire us, stories that showcase the best of humanity rather than the worst. I know it sounds small, almost naive. But media outlets watch closely what their audience engages with; their survival depends on it. By shifting what we choose to amplify, we can nudge the focus away from fear-driven narratives toward stories of generosity, kindness, and connection. There is a whole section in the book named <strong>What Mainstream Media Can Do</strong>.</p>
<p>So, yesterday's night code rush led me to create the <a href="https://mas.to/@TheBrightSide"><em>The Bright Side</em> bot on Mastodon</a>. Using .NET and AI, the bot curates and publishes microblogging threads featuring feel-good stories from news sites. It's my small attempt to bring more bright news into people's social media feeds, injecting a little bit of hope to counterbalance the flood of negativity. I wanted to create something that could act as a beacon, even if it's just a tiny spark in the vast online darkness.</p>
<p>It's about creating a new kind of feedback loop—one where positive stories aren't the outliers but the norm. Where tales of everyday acts of kindness get as much attention as tales of conflict. This doesn't mean ignoring the bad or pretending problems don't exist. It's about balance. It's about making space for the good and acknowledging that the world is filled with generosity, even if it doesn't always make the headlines.</p>
<p>Next time you find yourself being pulled into a negative news spiral, pause. Take a moment to seek out stories that lift you up instead of dragging you down. Share them. Talk about them. Not just to counteract the bad, but to remind yourself—and others—that there is so much good out there waiting to be recognized.</p>
<p>In a world where fear sells, let's be the ones who choose to spread hope. Because if fear can be infectious, then so can generosity.</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
    </item>
    <item>
      <title>A Guide to Implementing ActivityPub in a Static Site (or Any Website) - Part 7</title>
      <link>https://maho.dev/2024/11/a-guide-to-implementing-activitypub-in-a-static-site-or-any-website-part-7/</link>
      <pubDate>Fri, 08 Nov 2024 01:24:39 +0000</pubDate>
      <guid>https://maho.dev/2024/11/a-guide-to-implementing-activitypub-in-a-static-site-or-any-website-part-7/</guid>
      <description>You can find the index and other parts of this series here . We are almost done! Thank you for coming all the way into this journey. In this part we will learn ...</description>
      <content:encoded><![CDATA[<blockquote>
<p>You can find the <a href="https://maho.dev/2024/02/a-guide-to-implement-activitypub-in-a-static-site-or-any-website/">index and other parts of this series here</a>.</p>
</blockquote>
<p>We are almost done! Thank you for coming all the way into this journey. In this part we will learn how to broadcast (aka federate) your site posts to your folowers.</p>
<h2 id="overview">Overview</h2>
<p>The federation of your posts (aka. sending your posts to live infitely in the fediverse) is pretty straighforward:</p>
<p>{{&lt; mermaid &gt;}}
sequenceDiagram
BroadcastTool-&gt;&gt;Storage: Retrieve followers (actor uris)
BroadcastTool-&gt;&gt;Follower-instance: Get actor info (including inbox uri)
BroadcastTool-&gt;&gt;Filesystem: Get note json (post)
BroadcastTool-&gt;&gt;Follower-inbox: Send a create action (wrapper of note)
{{&lt; /mermaid &gt;}}</p>
<p>In a few words, you will iterate your list of followers, get their inbox url, and send a request to such url to create an fediverse object (note, article, etc.).</p>
<h2 id="details">Details</h2>
<p>On <a href="https://maho.dev/2024/02/a-guide-to-implementing-activitypub-in-a-static-site-or-any-website-part-4/">part 4</a>, we already generated the notes. These are static files living in our site. However, we need to send such notes to the fediverse again, so they are aware that these exists.</p>
<p>Once a note hits one fediverse server, it is very likely that if this object gets shared and visible to other instances, these instances will already be aware of your post. Your notes are being federated. So how a fediverse instance know if a note is already in their database? With the object id. This is what a create note looks like:</p>
<pre><code class="language-json">{
  &quot;@context&quot;: &quot;https://www.w3.org/ns/activitystreams&quot;,
  &quot;id&quot;: &quot;https://hachyderm.io/users/mapache/statuses/111876223052500122/activity&quot;,
  &quot;type&quot;: &quot;Create&quot;,
  &quot;actor&quot;: &quot;https://hachyderm.io/users/mapache&quot;,
  &quot;published&quot;: &quot;2024-02-05T01:14:48Z&quot;,
  &quot;to&quot;: [
      &quot;https://www.w3.org/ns/activitystreams#Public&quot;
  ],
  &quot;cc&quot;: [
      &quot;https://hachyderm.io/users/mapache/followers&quot;,
      &quot;https://maho.dev/@blog&quot;
  ],
    &quot;object&quot;: {
       &lt;original note&gt;
    },
    &quot;signature&quot;: {
        &quot;type&quot;: &quot;RsaSignature2017&quot;,
        &quot;creator&quot;: &quot;https://hachyderm.io/users/mapache#main-key&quot;,
        &quot;created&quot;: &quot;2024-02-05T01:14:48Z&quot;,
        &quot;signatureValue&quot;: &quot;dYIs1ZFExg5LeqHrZ5o905/fDv7WGyxC71kl1Jjlq9kqSDjMlhXP3/159fgwF+ouJRjBWEOIUUAwHhHFmbcuMbqFNxxPZ1nQ6A10HSL8osb4CycUsTOtu0XoOp2x5eufB8dgsVqb8LfCOlpGyuNLewbAheqUN3mjO+QyaKQJ0PTRqxKt5gUcAlpkGYTGLx8f+b9dIwHUM9F4VzueWJ7UiVqOMxS1Lb7mXn2qmEyj3hN81W5jttHyUmQ+qB5nNjKc/u8eOGcV+p5yy/auCSq4im2JsvmwK4fCpsfC2gqSl4gRknTng8T/DAoVUbW1bQC5YJg14uqeEKN5QGkqZxWikA==&quot;
    }
}
</code></pre>
<p>This id is unique (&quot;https://hachyderm.io/users/mapache/statuses/111876223052500122/activity&quot;) and in the example above is a mastodon generated one. At difference that the original note or object, these does not need to be reachable, in my case I get away with just adding &quot;/create&quot; to the original id. But it is important this id is consistent, to avoid generating multiple notes.</p>
<p>Another important part to mention is the cc, which I have not figure it out yet, honestly. Mastodon seems to either ignore this, probably because it already has a database of who follows who, and decides to show the timeline of posts not based in the create requests received, but based in what should they see. In any case, is important to at least cc your followers url.</p>
<p>Finally the object, really important that the signature matches the object.</p>
<p>One important aspect that I am figure it out is the &quot;published&quot; field. This can be different from the date the note is mention to publish. But I am pretty sure, mastodon uses this to decide if a post will hit the &quot;#hashtag&quot; list or timeline. Mastodon seems to determine if the post is &quot;recent&quot; so it appears in the hashtags. I have not mastered this yet, so I may have an update weeks later.</p>
<p>Another important consideration is taht, if you have more than one follower with the same inbox, you don't need to send the same note again.</p>
<h2 id="putting-everything-together">Putting everything together</h2>
<p>I have implemented this in my favorite language (C#), but should be straighforward to implement in other places. You can find the code (and contribute) <a href="https://github.com/mahomedalid/almost-static-activitypub/blob/main/src/BroadcastPost/">here</a>.</p>
<p>Remember, as long as the id is the same, there is no harm in sending the same create note again. It won't get duplicated, but to avoid wasting bandwith, processing power and bytes, you don't want to do this. This is what my bash script to broadcast looks like:</p>
<pre><code class="language-bash"># hugo deploy code ...
# azure deploy code ...

# at this point the generated notes are in public/socialweb/notes of the local runner (local dev or cicd worker)

for file in public/socialweb/notes/*; do
    # getting the base filename
    fileName=$(basename &quot;$file&quot;)
    
    # echo for logging purposes
    echo &quot;socialweb/notes/$fileName&quot;

    # Use Azure CLI to set content type of the note
    az storage blob update -c &quot;\$web&quot; -n &quot;socialweb/notes/$fileName&quot; --content-type &quot;application/activity+json;&quot; --account-key &quot;$AZURE_STORAGE_KEY&quot; --account-name &quot;$AZURE_STORAGE_ACCOUNT&quot;

    # if the note is in the file &quot;broadcasted.txt&quot; then we don't need to broadcast it again
    grep -q &quot;$fileName&quot; broadcasted.txt &amp;&amp; continue
    
    # Broadcast post
    BroadcastPost --notePath $file

    # Add the note to the list of broadcasted notes
    echo &quot;$fileName&quot; &gt;&gt; broadcasted.txt
done
</code></pre>
<p>As you can see the BroadcastPost command does all the magic. The broadcast post needs these 3 environment variables:</p>
<pre><code>export ACTIVITYPUB_DOTNET_PRIVATEKEY=`cat privkey.pem`
export ACTIVITYPUB_DOTNET_KEYID=&quot;https://maho.dev/blog#main-key&quot;
export ACTIVITYPUB_DOTNET_STORAGE_CONNECTIONSTRING=&quot;DefaultEndpointsProtocol=https;...secrets...;EndpointSuffix=core.windows.net&quot;
</code></pre>
<h2 id="next-steps">Next steps</h2>
<ul>
<li>If you want to integrate this in a <a href="https://github.com/mahomedalid/static-activitypub-blog-template/blob/main/.github/workflows/cicd-blog.yaml">CI/CD check my example of github actions and some utility scripts here</a>.</li>
<li>Another alternative instead of using a broadcasted.txt local file, would be to create a table in a database (e.g. local sqlite) or in the same Azure Storage.</li>
<li>What happens if you update a post? There is a <code>update</code> action in activitypub, I have not implemented that yet, but the more complex part would be to &quot;detect&quot; changes on posts. Maybe with a hash? or datetime? Or maybe manually, idk.</li>
<li>I have some crazy ideas of how we should send different objects for the same origin. Keep posted, because that is some crazy shit.</li>
</ul>
<p>So, as you can see this is not super complicated, and I will in this moment broadcast this post.</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>Fediverse</category>
      <category>ActivityPub</category>
      <category>Static Sites</category>
      <category>Hugo</category>
      <category>Azure</category>
      <category>Mastodon</category>
      <category>Web Development</category>
      <category>Social Web</category>
      <category>WebFinger</category>
      <category>HTTP</category>
      <category>Azure</category>
      <category>AzureFunctions</category>
    </item>
    <item>
      <title>The Digital Umwelt. Beyond the noise and the crap content.</title>
      <link>https://maho.dev/2024/09/the-digital-umwelt.-beyond-the-noise-and-the-crap-content./</link>
      <pubDate>Wed, 04 Sep 2024 20:33:08 -0700</pubDate>
      <guid>https://maho.dev/2024/09/the-digital-umwelt.-beyond-the-noise-and-the-crap-content./</guid>
      <description>Back in May, while commuting the few miles from the rural area where I was staying in Mexico to the city for virtual meetings, I listened to a podcast episode a...</description>
      <content:encoded><![CDATA[<p>Back in May, while commuting the few miles from the rural area where I was staying in Mexico to the city for virtual meetings, I listened to a podcast episode about whales. It focused specifically on how scientists are leveraging technological advancements to understand and decode the complex language of certain whale species. The ultimate goal is to create a translator that would allow humans to communicate with these creatures. However, the most challenging aspect lies in grasping the context of their communication — in other words, their world.</p>
<p>Consider this: Have you ever tried explaining the fourth dimension to someone? You might find some videos online, but since we explore the universe through our senses, it’s incredibly difficult to truly comprehend what it would look and feel like, even though this dimension is part of our universe. Similarly, how could we possibly explain concepts like love to a whale, or understand their world when their sensory organs are so different from ours?</p>
<p>A few weeks later, I was recommended a book called An Immense World by Ed Yong. The first chapter felt incredibly connected (or maybe I was already biased) to this scenario, and it introduced me to a fascinating new concept: Umwelt.</p>
<p>Umwelt, as explained in Ed Yong's book, refers to the unique sensory world of every animal, including humans. Every species lives in its own perceptual world, shaped by its sensory organs and neurological architecture.For instance, a dog’s world is filled with smells that are invisible to us, while birds can see ultraviolet light that we cannot perceive. In the case of whales, their umwelt is shaped by senses we can barely fathom, like echolocation. They live in a completely alien reality. This gives us an idea of the challenge scientists face in trying to interpret whale communication - they are not merely deciphering sounds but attempting to enter an entirely different sensory world.</p>
<p>Imagine being able to hear the world through echolocation like Daredevil, or see ultraviolet light. It’s a cool idea, right? These abilities are explored in many comics, where humans possess sensory powers far beyond the norm. But take Madame Web, for example; she struggles to make sense of the overwhelming flood of information her abilities bring.</p>
<p>The reason we perceive the world differently from whales, dogs, and other creatures is, as you might have guessed, because we evolved in distinct ways. Each species has developed sensory perceptions that are most efficient for its survival. A key concept in the book An Immense World is that you <strong>don’t want</strong> to perceive everything. Perceiving more is actually detrimental to efficiency. If humans could see ultraviolet light or sense echolocation, we would likely be less efficient — perhaps even overwhelmed to the point of madness. Our senses are finely tuned to give us <strong>just enough information to thrive without becoming overloaded</strong>.</p>
<p>The whole 4-5 paragraphs before of this is to explain this idea, so I will repeat it once again: Our senses are finely tuned to give us <strong>just enough information to thrive without becoming overloaded</strong>.
And you know what? I'm not a scientist, and I'm not here to talk about the book or animals. I'm here to pitch a new concept: The Umwelt of Internet Content.</p>
<p>I used to fill my RSS feeds with every major and interesting website I could find—Slashdot, personal blogs, technical blogs, news sites. I still follow some of them, along with newer ones like Hacker News. One of my daily goals was to go through all the articles, every single one, to ensure I didn’t miss anything. I wanted to stay ahead of each new technology stack, framework, and idea. I use to follow hundreds if not thousands of twitter accounts. I was overwhelmed, yet I was determined to consume everything as fast as I could. If I could go back in time, I’d slap myself in the face to realize that I was drowning in useless information. What I really needed was to curate the content that mattered to me.</p>
<p>I've heard this expressed differently: It's not the content you consume that matters, but <strong>the content you don’t consume</strong>. The power of saying &quot;no.&quot; The internet is a vast universe of information, but that universe is shaped by what you choose to engage with and the social networks you frequent. Your Umwelt—your personal perceptual window to the world—is defined by the content you allow in. Just as our biological senses are fine-tuned to avoid overload, we need to be just as selective with the digital content we consume. By curating our digital environment, we can focus on what truly matters, enhancing our understanding and avoiding the chaos of unnecessary noise. And each one of us will have a different Umwelt, <strong>and that is fine</strong>. We are different; we come from different places and have different opportunities. Our goals vary as well. To survive, we need to make our sensory world as efficient as possible.</p>
<p>And there’s a lot of noise out there. A lot of crap, garbage, and AI-generated content. Many unoriginal voices are willing to do anything to get engagement—likes, clicks, whatever it takes. We're constantly bombarded with meaningless motivation. As consumers of content, we must be careful about what we choose to consume and engage with. Community is one of the best ways to filter content—finding like-minded people with strong bonds and connections—but it’s not the only way. As content creators, we also need to be mindful of this. Do we really want to be in the Umwelt of everyone? Every creature on earth perceives the sun differently—a lizard, for example, experiences it differently than a shark. If you did not get it, this analogy talks about how content is perceived differently by different people. When you reach a large enough audience, there’s no such thing as homogeneous content—you’ll have haters, fans, followers, and friends. Is that really what you want as a content creator?</p>
<p>My belief is that it’s not. You need to be intentional about the channels through which you deliver your content. Just as writing a book is different from making a movie based on that book, creating a YouTube video is not the same as writing a LinkedIn post. We need to be deliberate about the platforms we use to reach the people we want to connect with. The goal should be to talk about what truly matters to us, rather than trying to appeal to the largest possible group. Instead of trying to be everything to everyone—blinding or annoying people with a broad approach—we should focus on being a limited set of frequency bands in the light spectrum, reaching only those who resonate with our message. You’ll want to find your niche, where you’re genuinely happy and passionate about the content you create.</p>
<p>Your voice is unique, and that’s something worth protecting. Content creators have the power—and the responsibility—to shape the digital landscape with intentionality. AI can be a powerful tool, but it should serve as an extension of your creativity, not a substitute for it. By being deliberate about how we use AI and the content we produce, we can avoid contributing to the flood of meaningless noise online. Instead, we can create work that reflects our true passions and resonates with our audience.</p>
<p>Don’t settle for adding more clutter to the internet; be better, have a purpose, tell your story, cut the crap. And maybe you did not like it this article, is fine, it should not be in your universe, but if you do, subscribe, and welcome this publication to your Umwelt.</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
    </item>
    <item>
      <title>My coffee history</title>
      <link>https://maho.dev/2024/09/my-coffee-history/</link>
      <pubDate>Tue, 03 Sep 2024 20:33:08 -0700</pubDate>
      <guid>https://maho.dev/2024/09/my-coffee-history/</guid>
      <description>My relationship with coffee probably began in my mother&apos;s womb. She may deny it, but I am pretty sure she drank more coffee than was advised or allowed in the 1...</description>
      <content:encoded><![CDATA[<p>My relationship with coffee probably began in my mother's womb. She may deny it, but I am pretty sure she drank more coffee than was advised or allowed in the 1980s. My mother is from a very small town in the middle of the mountains on the Pacific side of Mexico, very close to a semi-active volcano. My memories of visiting my grandmother are actually memories of smells: the smell of ashes and burnt food cooked with wood, ashes in the meat and the tortillas, and coffee.</p>
<p>My mother would probably say that my first encounter with coffee was at 3 years old. There may be a physical picture, but the image that lives in my head is of a little kid, using my small hands to mix baby food into coffee with milk, just to create the messiest concoction possible. As she would gladly tell you many times, I was unable to drink milk from just a few days old, not because I was lactose intolerant, but because I did not like the flavor of it. So she tried -very hard- different ways to mask the flavor, and one of them was coffee.</p>
<p>Coffee in my childhood was always a supporting character in the main story. It wasn't even a fancy or pleasant character; it was a soluble powder that required unhealthy amounts of sugar and a hint of milk to make it taste good. I loved it at the time, and I'm not ashamed of my humble beginnings—it was all I knew. On rainy afternoons in May, I was responsible for making a two-block journey to the store to buy bulk cookies and a single or double cup package of coffee for me and my mom. The coffee was a pretext, a small escape when money was short.</p>
<p>I was not unfamiliar with good coffee, though. Those occasions arose during funerals, very occasional restaurant visits, but mainly at my grandmother's house in the small town, where the coffee was prepared in the Mexican way known as &quot;café de olla,&quot; which simply means &quot;pot coffee.&quot; It's a mix of coffee grounds, cinnamon, and piloncillo (solidified cane juice), which came from the closest sugar cane factories, adding more ashes to the already volcanic town. I still enjoy café de olla sometimes, although it's too sweet for my current taste.</p>
<p>Fast forward, I went to university, and as we say in Latin America, to be young and not revolutionary is almost a biological contradiction. I was involved in subversive (although completely legal) student progressive movements, which often involved late-night meetings or visits to local coffee shops frequented by politicians and elderly people. It was during this time that my taste for coffee began to refine, to the point where I completely eliminated sugar to free my taste buds to a new world of sensations. It was also a time when I worked for a newspaper as a graphic designer, every evening after college, ending my shift after midnight. It was a time of coffee, not only to keep me awake but also to socialize with other journalists, especially one who was almost 70 years old at the time. He meticulously prepared his coffee each evening in his own drip machine, and through him, I learned about the care one should give to the preparation. I am afraid to think he is no longer living in this world, so I prefer to think his passion for coffee and the art of its preparation still lives somewhere.</p>
<p>After college, I moved to another city to escape, explore the world, and devour it. This involved not only living alone but also experimenting with more complex flavors. I became the designated coffee maker in my 2-bedroom, 5-person apartment, and I was the coffee officer in the startup where I worked. I experimented with different types, amounts, and grind levels, each morning before everyone arrived. This was a time of explosion—of coffee with shisha in hookah lounges, of learning the thermodynamics of plastic and water to prepare coffee while tent-camping on the beach. This was also a time of learning about coffee regions, altitudes, humidity, and traveling to places like Michoacán and Chiapas, world-famous for coffee quality in Mexico. From then on, coffee was no longer a second-class character in my world.</p>
<p>Coffee was no longer the nostalgic drink of funerals as my older relatives started passing away. I experimented with different preparation methods, like the &quot;Cuban press&quot; (aka Italian press) that I acquired shortly after getting married and used to prepare coffee for my wife every morning. Or the French press, which I got after we adopted a dog, and which I used before she asked me to go for a walk.</p>
<p>We moved to the US in 2016 to the Puget Sound, without knowing that Seattle is somewhat of a coffee capital of the world. I enjoy visiting local coffee shops (technically Starbucks can be considered local, although that's not what I mean). With just the two of us in a new country and shortly after she got pregnant, I received for my birthday one of those very fancy-looking coffee dripper filters for one person and a spice grinder.</p>
<p>I still went back to Mexico a few times for vacations, one of them to make artisanal coffee. And by that, I don't mean just making a cup of black magma; I mean cleaning the beans with only air and gravity, roasting them with just an iron pot, and grinding them with a corn-grinder machine. I still envision creating my own brand in the future.</p>
<p>I have been fortunate in this life, I am grateful. I recently acquired a brand new Breville Barista, and I enjoy making coffee every day. I sometimes joke that I could live without everything—beer, meat, seafood, sugar—but not coffee. But it's not really a joke.</p>
<p>You could say that I am more of a coffee snob today; I only buy whole bean bags, and I haven't touched soluble coffee in years. I have changed, yes, but coffee around me has changed as well. While I may have become more discerning in my taste for coffee, it's the essence of coffee itself that has transformed, enriching my life in ways I never imagined possible.</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
    </item>
    <item>
      <title>Career Advice From People I Admire</title>
      <link>https://maho.dev/2024/07/career-advice-from-people-i-admire/</link>
      <pubDate>Wed, 17 Jul 2024 20:33:08 -0700</pubDate>
      <guid>https://maho.dev/2024/07/career-advice-from-people-i-admire/</guid>
      <description>I am incredibly grateful to the people who have helped shape me into who I am today. Their support and guidance have been invaluable. But advice, like wisdom, c...</description>
      <content:encoded><![CDATA[<p>I am incredibly grateful to the people who have helped shape me into who I am today. Their support and guidance have been invaluable.</p>
<p>But advice, like wisdom, can be tricky. It's not always suitable for everyone, and any phrase can become wisdom with the right context. Generic advice from sources like LinkedIn can often be too broad or superficial, and should be taken with a grain of salt. While I have a wealth of wisdom to share, I will save that for my one-on-one conversations.</p>
<p>That's why I am asking the people who have contributed to my growth not what advice they would give to others, but what advice they would give to their younger selves.</p>
<p>I will update this index with more details about the purpose of this video series. For now, I want to highlight that my goal is to document the career advice of people I admire.</p>
<ul>
<li>Emmeline Hoops - Sofware Engineer @ Microsoft - Video: <a href="https://www.youtube.com/shorts/VLJvLx1tXzQ">Embrace Chaos</a> - <a href="https://www.linkedin.com/in/ehoops/">LinkedIn</a></li>
<li>Olha Konstantinova - Software Engineer @ Microsoft - Video: <a href="https://www.youtube.com/shorts/81sUZeQIgDA">Get a mentor</a> - <a href="https://www.linkedin.com/in/olha-konstantinova/">LinkedIn</a></li>
<li>Marcia Dos Santos - PM @ Microsoft - Video: <a href="https://www.tiktok.com/@theraccoonbytes/video/7380138633942076715">Work life balance in the beach</a> - <a href="https://www.linkedin.com/in/marciaalessandra/">LinkedIn</a></li>
<li>Zach Miller - Software Engineer @ Microsoft - Video: <a href="https://www.youtube.com/watch?v=N6al5QEgaVo">Be kind to yourself</a> - <a href="https://www.linkedin.com/in/zachmiller93/">LinkedIn</a></li>
<li>Yani Ariunbold - Sofware Engineer @ Microsoft - Video: <a href="https://www.tiktok.com/@theraccoonbytes/video/7384916932425813291">College is to explore</a> - <a href="https://www.linkedin.com/in/yariunbold/">LinkedIn</a></li>
<li>Renato Marciano - Sofware Engineer @ Microsoft - Video: <a href="https://www.tiktok.com/@theraccoonbytes/video/7391493488438922542">Stay curious</a> - <a href="https://www.linkedin.com/in/renatomarciano/">LinkedIn</a></li>
</ul>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>careerDevelopment</category>
      <category>tech</category>
      <category>womenintech</category>
    </item>
    <item>
      <title>If someone copies your content, it&apos;s a good sign.</title>
      <link>https://maho.dev/2024/07/if-someone-copies-your-content-its-a-good-sign./</link>
      <pubDate>Tue, 02 Jul 2024 20:33:08 -0700</pubDate>
      <guid>https://maho.dev/2024/07/if-someone-copies-your-content-its-a-good-sign./</guid>
      <description>&amp;quot;Imitation is the sincerest form of flattery,&amp;quot; I often say when someone tells me they&apos;re mad because someone stole their idea or post. It&apos;s a poor con...</description>
      <content:encoded><![CDATA[<p><strong><em>&quot;Imitation is the sincerest form of flattery,&quot;</em></strong> I often say when someone tells me they're mad because someone stole their idea or post. It's a poor consolation, I know. There's real anger when you pour effort into something, and someone else takes credit. I've felt that anger many times. Just a few weeks ago, I saw someone on LinkedIn lamenting that another person had stolen their idea, got 40k likes, and then blocked the original author. It's unfair, yes—a reflection of the world's inherent unfairness.</p>
<p>Copy-catting is rampant in content creation, especially among illustrators, musicians, and other artists. Artists have grappled with this for decades, prompting the creation of copyright laws. Gen-AI threats have already worsened this respect. But I didn't truly understand the coolness of being copied until I co-founded a startup with my partner.</p>
<p>We joined a few incubators, which helped to prepare our projects with extensive consulting on marketing, management, etc. Eventually, we pitched our projects to venture capital investors. You’ve seen this story on shows like SharkTank or in YouTube and TikTok videos: someone delivers an elevator pitch (named for the time it takes to ride an elevator) and then fields questions before investors decide to fund them. It is a rewarding experience that makes you humble.</p>
<p>Among all the feedback and questions, one always stuck with me: &quot;What will stop me from going out there and creating the very same product myself?&quot; It's a hard question—devastatingly cold the first time you hear it. Basically, it asks: <strong>What makes you so special?</strong></p>
<h2 id="ideas-are-not-unique">Ideas are not unique</h2>
<p>Ideas, you see, are not special by themselves. I have a friend who often grumbles that someone implemented his business idea, claiming we all told him he or his idea was crazy (we never did). Even monumental ideas like electricity or computers aren't unique and did appear in multiple places simultaneously. Content creation is no different. What makes you and your content so special? What stops me from copying your content and pasting it on my timeline? <em>Spoiler alert: there is something.</em></p>
<p>While ideas aren't special and content isn't unique, the people behind them are. Similarly, in startups, a high-performing creative team is often more valuable than the product it creates. Companies are frequently acquired to embrace and absorb (and sometimes extinguish) their teams, which they see as a threat, not just to reuse their products. Big Tech has many examples of this kind of adquisitions. What's stopping Apple from Sherlocking apps?</p>
<p>If you’re unfamiliar with “Sherlocking,” here's a quick recap: Apple's predecessor to its Spotlight search feature in macOS was Sherlock, which searched the Mac and included some web-searching capability. A developer named Dan Wood created an app called Watson to supplement Sherlock by expanding its Internet search features. Watson was popular and successful—until Mac OS X 10.2, when Apple integrated everything Watson did right into Sherlock, eliminating the need for Watson. The app was effectively <strong>Sherlocked</strong>. Now each year there’s a <a href="https://9to5mac.com/2024/06/12/here-are-the-apps-sherlocked-by-apple-during-wwdc-2024/">list of apps released that Apple has sherlocked during WWDC</a>.</p>
<p>As I said before, this applies to everything from content to diets. I recently learned this from a famous nutritionist online. She had a successful diet and exercise program with a close-knit community. However, she focused so much on protecting her content from being copied that she neglected (and even attacked) the community members, and her numbers started to decline. Diets can be easily replicated, and by &quot;easy,&quot; I mean that others can replicate it, but I don't want to disregard all the effort and experience required to create one from scratch. <strong>That is the true value—the value behind who created those diets, the value of the person, not just the content itself</strong>.</p>
<h2 id="niches-can-be-beneficial">Niches can be beneficial</h2>
<p>That is why, you will see <strong>niches</strong> can be beneficial. Sometimes, it's preferable to have a few loyal customers (followers are customers) who engage deeply and are willing to pay well, rather than millions of disengaged customers.</p>
<p>Conversion rates matter. They are a crucial metric that can determine the success or failure of any business or content creation endeavor. Conversion rates measure how many of your audience members actually take the desired action—whether it's making a purchase, subscribing to a newsletter, or engaging with your content. A high conversion rate indicates that your message resonates with your audience, leading to greater engagement and profitability. Conversion rates in niches are greater. You may not don't need millions of followers, you just need 1000 in your niche. If you have time <a href="https://www.youtube.com/watch?v=5zUndMfMInc">Jack Conte explains this very nicely in the <strong>Death of the Follower &amp; the Future of Creativity on the Web</strong></a>.</p>
<p>This brings us to the concept of blue waters versus red waters. Blue waters represent untapped markets, fresh opportunities where competition is minimal. Here, you have the chance to innovate, to carve out a unique niche, and to attract a dedicated following. In blue waters, your ideas can flourish with less fear of immediate imitation or saturation.</p>
<p>On the other hand, red waters are markets teeming with competition—think of them as shark-infested waters where everyone is fighting over the same small piece of bloody meat. In such environments, differentiation becomes challenging, and standing out requires extraordinary effort and creativity. You’re constantly defending your territory and trying to outdo countless others who are vying for the same audience’s attention. Going back to the LinkedIn story of the beginning that is a red water, that content attracted 40k likes, the content that everybody is fighting to create in such social network.</p>
<p>Navigating between blue and red waters is a strategic decision. While blue waters offer open spaces and new possibilities, they also come with the risk of the unknown. Red waters, though competitive, provide a clear picture of what works and what doesn't. However, staying in red waters can lead to burnout and diminished returns if the market becomes too crowded and cutthroat.</p>
<p>Understanding where to position yourself can significantly impact your conversion rates. <strong>By finding your unique space in blue waters, you can build a loyal audience who values your originality and innovation.</strong> Conversely, in red waters, you must leverage exceptional strategies and continuously evolve to <strong>maintain a high conversion rate amidst fierce competition</strong>. You face the threat of burnout.</p>
<h2 id="immitation-means-that-it-is-already-working">Immitation means that it is already working</h2>
<p>Market viability is another key concept. If someone copies your product or content, it means there's a market for it. It indicates that other people are interested, and with billions of people out there, you can carve a niche for yourself. This is a positive signal that your idea has value and appeal. Even Bing, which holds only a single-digit market share, has proven to be profitable for Microsoft. This example shows that even a relatively small piece of a large market can be lucrative if managed well.</p>
<p>Market viability underscores the importance of differentiation. When others copy your product, it forces you to innovate and continually improve your offerings. <strong>Those who copy won't have your creativity, and the way you can reinvent yourself.</strong> This competition can drive you to refine your unique value proposition, making your product or content even more compelling. It encourages you to find new ways to engage your audience and to offer something that <strong>your competitors can’t easily replicate</strong>. And each time you do it, it is get noticed.</p>
<h2 id="fuck-copyright">Fuck Copyright</h2>
<p>Of course, certain technologies can be patented, representing intellectual property. Copyright laws were created for a reason: to protect the creators' rights and ensure they benefit from their inventions and creative works. But sometimes, I feel like saying, &quot;Fuck copyright, fuck patents, welcome innovation, welcome creativity.&quot; This sentiment stems from the belief that overly rigid intellectual property laws can stifle creativity and hinder the free flow of ideas.</p>
<p>I LOVE when creators or companies collaborate. Collaboration and open exchange of ideas can lead to breakthroughs that benefit everyone, not just a select few. This is not to say that creators shouldn't be rewarded for their work, but rather that the balance between protection and openness needs to be carefully managed to encourage true innovation.</p>
<p>I follow my favorite people on LinkedIn, Twitter, Mastodon, and TikTok not just for their content but for who they are. That uniqueness is irreplaceable. It’s their personality, their perspective, and their way of engaging with the world that draws me in. The authenticity they bring to their platforms cannot be copied or faked. It's this genuine connection and individuality that make their content stand out in a sea of trash.</p>
<p>When you follow someone for who they are, you’re not just consuming content; you’re engaging with a vision and theiur way of thinking. This deeper connection fosters loyalty and a sense of community. It is being part of this community what it matters, not how funny was that last joke.</p>
<p>This is why I value the creators I follow—because they offer more than just information or entertainment; they offer a piece of themselves. And I will buy them a coffee or a beer anytime without a hint of hesitation.</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
    </item>
    <item>
      <title>Do not prioritize efficiency over nuance, fuck Gen-AI</title>
      <link>https://maho.dev/2024/06/do-not-prioritize-efficiency-over-nuance-fuck-gen-ai/</link>
      <pubDate>Tue, 18 Jun 2024 20:33:08 -0700</pubDate>
      <guid>https://maho.dev/2024/06/do-not-prioritize-efficiency-over-nuance-fuck-gen-ai/</guid>
      <description>I used to write a lot when I was younger, even as a kid. There was no Gen-AI, no Grammarly, no advanced tools to assist with spelling or grammar. It was just me...</description>
      <content:encoded><![CDATA[<p>I used to write a lot when I was younger, even as a kid. There was no Gen-AI, no Grammarly, no advanced tools to assist with spelling or grammar. It was just me, a pen, and paper, pouring out my thoughts. I would lose myself in the worlds I created, and I would revisit some of them. I used to write speeches, poems, and essays. Words were my world, a tiny one-person world. Code became a form of expression. Writing has always been my sanctuary, a place where thoughts can flow freely, and ideas can take shape.</p>
<p>Gen-AI has brought a uniform style to some of the things I read lately. After some time using tools like ChatGPT, you start detecting the patterns. Search for &quot;Demystifying,&quot; and you will see. And sometimes I miss that raw, personal, unique touch in everything. In today's world, where technology plays an ever-expanding role in shaping how we communicate, there is a temptation to prioritize efficiency over nuance. Tools like that risk diluting the individuality that once defined personal expression.</p>
<p>Convenience and efficiency have overshadowed creativity. Colors have disappeared as in cars—white, black, red, and gray cars dominate the market, and it is increasingly difficult to find the purples, yellows, and blues. The imperfections, the quirks, and the genuine voice are becoming more appreciated, and it is what will persevere. It is very difficult to replicate individuality, making those unique touches even more valuable in a world where uniformity is on the rise. So, I decided to be that, raw and authentic, imperfect connection.</p>
<p>But the other side is a double-edged sword. Words have an impact; I have forgotten that in the last few days. I have become lazy, carefree, raw, and unfiltered. I have said &quot;f*ck&quot; on LinkedIn, brain-dumped to my friends, and led my teammates by the unwise example of unhealthy discussions. People are not machines, or virtual entities. I am grateful, though, that my teammates and friends can tell me when I am communicating wrong. Feedback is a gift if you know how to listen. I would always remember, for example the time my PM told me to not call her girl.</p>
<p>And feedback can be wake-up call to reconnect with the thoughtful, deliberate side of writing and to remember the power that words carry. As I continue to evolve as a professional, I embrace technology while holding fast to the belief that the human touch—genuine, heartfelt expression—is what truly resonates with others, even if it's sometimes in a negative way.</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
    </item>
    <item>
      <title>A Historic Shift: How Mexico&apos;s First Woman President Challenges Toxic Masculinity</title>
      <link>https://maho.dev/2024/06/a-historic-shift-how-mexicos-first-woman-president-challenges-toxic-masculinity/</link>
      <pubDate>Mon, 03 Jun 2024 20:33:08 -0700</pubDate>
      <guid>https://maho.dev/2024/06/a-historic-shift-how-mexicos-first-woman-president-challenges-toxic-masculinity/</guid>
      <description>Today was a historic day for Mexico, as the country elected its first woman president. I love Mexico—the culture, food, friends, and family—but it has always be...</description>
      <content:encoded><![CDATA[<p>Today was a historic day for Mexico, as the country elected its first woman president. I love Mexico—the culture, food, friends, and family—but it has always been a country with deep-rooted toxic masculinity. This event is truly historic and unexpected.</p>
<p><img src="/img/womanintech.webp" alt="A conceptualization of a woman in tech" /></p>
<p>I don't expect much to change immediately in terms of politics, although I hope for significant progress. It won't erase machismo overnight. Women all over Mexico are not only still underpaid and undervalued but also face violence and disappearances. They are getting killed and mistreated. I am not physically violent, but I used to embrace that toxic masculinity without realizing it. Each time I return to Mexico, it becomes evident to me how normalized such behaviors are, sometimes even among my close friends and family, in work settings, and in general society.</p>
<p>Paradoxically, in Mexico, we have a deep and almost religious respect for our mothers. Big fights can be diffused with the power of a mother's flop- joke, but it is true. Mothers can work miracles in Mexico and still do. As example the current president went to visit the mother of one of the most famous cartel leaders, el Chapo, and stretched her hand. This is why our culture is so surreal.</p>
<p>I am deeply grateful to my partner for helping me identify certain traits that kept me inside that vicious circle. She is very patient, and most of the things I have learned about breaking free from toxic masculinity come from her guidance and support. She has taught me to be more self-aware and to challenge my own behaviors and beliefs, fostering a more respectful and equal environment.</p>
<p>I want to share some tips I've learned to break this cycle:</p>
<ul>
<li><p>Unconscious Bias. It's not that men don't understand what unconscious gender bias is—logically, we can grasp it. But because of its unconscious nature, we cannot always detect it. I need to ask myself each time, &quot;Am I being unfair? Am I thinking this way because of a bias I don't recognize?&quot; My kid always asks questions that open my eyes to new perspectives.</p>
</li>
<li><p>Mansplaining. Before I want to jump in and explain something, I need to stop myself and approach the situation with curiosity. Like a detective, I try to ask why the other person thinks a certain way. Asking questions allows me to understand their point of view before trying to explain something that may not need explaining.</p>
</li>
<li><p>Active Listening. One of the most important things I've learned is the value of truly listening. This means paying full attention to what someone is saying without preparing your response in advance or interrupting them. I always like to solve things and get things done, but that prevents me from listening. I've learned that listening is also an act of respect and trust. By actively listening, I can better understand the perspectives and feelings of others, which helps foster mutual respect and break down ingrained biases.</p>
</li>
<li><p>Challenging Stereotypes. It is crucial to question and challenge gender stereotypes that we encounter in everyday life. Whether through media, language, or social interactions, being aware of and actively opposing stereotypes helps dismantle the harmful narratives that perpetuate toxic masculinity. This includes speaking up when I see or hear something that reinforces these stereotypes. Be an ally whenever you can, and challenge stereotypes yourself.</p>
</li>
<li><p>Promoting Equality at Home and with Friends. Change begins at home, and promoting gender equality within my household has been a significant step. This means sharing household responsibilities equally, encouraging my child to pursue their interests regardless of gender norms, and demonstrating respectful behavior toward all family members and friends. If he wants that pink, rainbow unicorn purse, so be it. If he wants to play any game he likes, so be it. By modeling equality at home, I hope to instill these values in the next generation and contribute to a more inclusive society.</p>
</li>
</ul>
<p>I want to help break that cycle in tech, and hopefully next generations will get the same opportunities, no matter who they are.</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>GenderEquality</category>
      <category>mexico</category>
      <category>careerDevelopment</category>
      <category>WomenEmpowerment</category>
      <category>CulturalChange</category>
    </item>
    <item>
      <title>A Guide to Implementing ActivityPub in a Static Site (or Any Website) - Part 6</title>
      <link>https://maho.dev/2024/04/a-guide-to-implementing-activitypub-in-a-static-site-or-any-website-part-6/</link>
      <pubDate>Mon, 15 Apr 2024 01:24:39 +0000</pubDate>
      <guid>https://maho.dev/2024/04/a-guide-to-implementing-activitypub-in-a-static-site-or-any-website-part-6/</guid>
      <description>You can find the index and other parts of this series here . Let&apos;s dig in into the most controversial part of my guide: the inbox. As far as I know, there is no...</description>
      <content:encoded><![CDATA[<blockquote>
<p>You can find the <a href="https://maho.dev/2024/02/a-guide-to-implement-activitypub-in-a-static-site-or-any-website/">index and other parts of this series here</a>.</p>
</blockquote>
<p>Let's dig in into the most controversial part of my guide: the inbox. As far as I know, there is no way to make this inbox static. However, thanks to the fact that ActivityPub does not require the inbox to share the same domain as the other elements, we can host it anywhere.</p>
<p>I believe it could be controversial for two reasons. First, it is not truly static; you require a backend somewhere running on some kind of server. The second reason is that I am using Azure, and I know from the get-go that will put some of you off. But I believe this guide is still valid, as you can easily deploy to AWS, GCP, or even Vercel. I am writing this in .NET, but as you may already know, translating the logic should be trivial for most modern languages. I will actually encourage people to contribute these implementations to my repo and make it <strong>our</strong> repo.</p>
<h2 id="overview">Overview</h2>
<p>The <code>Inbox</code> receives <code>POST</code> HTTP requests for different actions. These requests may or may not be signed, and you can decide to take action or not on those. There is no definition of what minimal implementation should be, and one example is how Threads implemented their federation in phases. You could implement just the follow feature, leaving your followers without any ability to unfollow you, but that is not cool. For a static site, I would suggest that the core should be follow/unfollow and replies.</p>
<h3 id="follow">Follow</h3>
<p>This is how the process looks like; we will go into the details of each step:</p>
<p>{{&lt; mermaid &gt;}}
sequenceDiagram
participant Follower-instance
participant Inbox
participant Storage
Follower-instance-&gt;&gt;Inbox: Follow request
Inbox-&gt;&gt;Follower-instance: 200 OK
Inbox-&gt;&gt;Follower-instance: Request actor info
Inbox-&gt;&gt;Storage: Save follower record
Inbox-&gt;&gt;Follower-instance: AcceptRequest
{{&lt; /mermaid &gt;}}</p>
<p>When a user requests to follow your site, they will send a request like this:</p>
<pre><code class="language-json">{
  &quot;@context&quot;: &quot;https://www.w3.org/ns/activitystreams&quot;,
  &quot;id&quot;: &quot;https://mastodon.social/64527582-3605-4d19-ac99-6715df3b0707&quot;,
  &quot;type&quot;: &quot;Follow&quot;,
  &quot;actor&quot;: &quot;https://mastodon.social/users/mictlan&quot;,
  &quot;object&quot;: &quot;https://maho.dev/@blog&quot;
}
</code></pre>
<ol>
<li>Context is the namespace for the JSON request.</li>
<li>Id is a unique ID for the follow request.</li>
<li>Type is the type of the message.</li>
<li>Actor is who is trying to follow you.</li>
<li>Object is what is trying to follow. In this case, your actor URI.</li>
</ol>
<p>The request itself should be answered by a <code>200 OK</code>, but that is not the end of the process, and will just mark the follow request as <code>pending</code> in the Mastodon or any other client side.</p>
<p>What I do with this is to create a record in my database (an Azure Table, but it can be anything, even SQLite). I only store the actor URI and nothing else.</p>
<p>The actor by itself is not useful, so you need to request more information about it. This is a <code>GET</code> request to the actor URI. The result will give you a lot of things (it actually returns what we saw in <a href="https://maho.dev/2024/02/a-guide-to-implementing-activitypub-in-a-static-site-or-any-website-part-3/">part 3 of implementing an actor</a>), but the most important one is the Public Key.</p>
<p>Once you store the actor and fetch extra information, you need to send a request to their inbox (this inbox URL comes from the actor object). This is an <code>AcceptRequest</code>, and this is how mine looks:</p>
<pre><code class="language-json">{
  &quot;@context&quot;: &quot;https://www.w3.org/ns/activitystreams&quot;,
  &quot;id&quot;: &quot;https://maho.dev/@blog#accepts/follows/mictlan@mastodon.social&quot;,
  &quot;type&quot;: &quot;Accept&quot;,
  &quot;actor&quot;: &quot;https://maho.dev/@blog&quot;,
  &quot;object&quot;: {
    &quot;@context&quot;: &quot;https://www.w3.org/ns/activitystreams&quot;,
    &quot;id&quot;: &quot;https://mastodon.social/64527582-3605-4d19-ac99-6715df3b0707&quot;,
    &quot;type&quot;: &quot;Follow&quot;,
    &quot;actor&quot;: &quot;https://mastodon.social/users/mictlan&quot;,
    &quot;object&quot;: &quot;https://maho.dev/@blog&quot;
  }
}
</code></pre>
<ul>
<li><strong>id</strong>: An unique ID of the accept request; it can be anything, just make sure it is unique.</li>
<li><strong>type</strong>: Accept string.</li>
<li><strong>actor</strong>: Who is making the request; in this case, your static site actor.</li>
<li><strong>object</strong>: The original follow request. <em>Tip: Make sure it is the original and not recreated (unserialized/serialized)</em>.</li>
</ul>
<p>This request should be signed; otherwise, it won't work. Mastodon is very fast to process any request. If you see the follow request not being reflected in the instance, it may be an issue with the signature. Mastodon will also return just a <code>200 OK</code> if the signature is valid, but may not process the message if any of the elements do not match wherever Mastodon is expecting.</p>
<p>You may find better documentation about the signature that ActivityPub instances are expecting, but this is the summary:</p>
<ol>
<li>Four request headers are necessary:</li>
</ol>
<ul>
<li>Host: with the host part of the follower actor inbox URI.</li>
<li>Date: UTC date string in the ISO format.</li>
<li>Digest: the SHA256 checksum of the whole payload in this format:</li>
</ul>
<pre><code class="language-text">SHA-256={payloadHash}
</code></pre>
<ul>
<li>Signature: A base64 encoded signed string using your private key. The signature string looks like this:</li>
</ul>
<pre><code class="language-text">(request-target): post {inboxUrl.AbsolutePath}\nhost: {inboxUrl.Host}\ndate: {date}\ndigest: {digest}
</code></pre>
<blockquote>
<p>Note: Remember to add the Content-Type &quot;application/activity+json&quot; element to your request.</p>
</blockquote>
<p>You can find my .NET implementation <a href="https://github.com/mahomedalid/almost-static-activitypub/blob/main/src/ActivityPubFunctions/Inbox.cs">here</a>.</p>
<h3 id="unfollow">Unfollow</h3>
<p>This is how the process looks like; we will go into the details of each step:</p>
<p>{{&lt; mermaid &gt;}}
sequenceDiagram
participant Follower-instance
participant Inbox
participant Storage
Follower-instance-&gt;&gt;Inbox: Unfollow request
Inbox-&gt;&gt;Follower-instance: 200 OK
Inbox-&gt;&gt;Storage: Remove follower record
Inbox-&gt;&gt;Follower-instance: Request actor info
Inbox-&gt;&gt;Follower-instance: AcceptRequest
{{&lt; /mermaid &gt;}}</p>
<p>The unfollow request is very similar to the follow request. For example:</p>
<pre><code class="language-json">{ 
  &quot;@context&quot;:&quot;https://www.w3.org/ns/activitystreams&quot;,
  &quot;id&quot;:&quot;https://mastodon.social/users/mictlan#follows/51971777/undo&quot;,
  &quot;type&quot;:&quot;Undo&quot;,
  &quot;actor&quot;:&quot;https://mastodon.social/users/mictlan&quot;,
  &quot;object&quot;:{
    &quot;id&quot;:&quot;https://mastodon.social/f722c435-5a8a-4e94-b7c8-529cdcdf12a6&quot;,
    &quot;type&quot;:&quot;Follow&quot;,
    &quot;actor&quot;:&quot;https://mastodon.social/users/mictlan&quot;,
    &quot;object&quot;:&quot;https://maho.dev/@blog&quot;
  }
}
</code></pre>
<p>You can see that the object element contains the whole Follow object. As in the follow part, you may want to check the signature of this request, since a bad actor could &quot;unfollow&quot; other people without their consent.</p>
<p>As in the follow request, you need to return a <code>2XX</code> HTTP code and send an AcceptRequest to the follower inbox to confirm the unfollow request. The AcceptRequest is exactly the same, and remember the object element just contains the whole original request, in this case, the undo request (which also includes an object inside).</p>
<p>Again, you can find the .NET implementation <a href="https://github.com/mahomedalid/almost-static-activitypub/blob/main/src/ActivityPubFunctions/Inbox.cs">here</a>.</p>
<h3 id="replies">Replies</h3>
<p>This is how the process looks like; we will go into the details of each step:</p>
<p>{{&lt; mermaid &gt;}}
sequenceDiagram
participant Follower-instance
participant Inbox
participant Storage
Follower-instance-&gt;&gt;Inbox: Reply to post
Inbox-&gt;&gt;Follower-instance: 200 OK
Inbox-&gt;&gt;Storage: Create the replies JSON file
{{&lt; /mermaid &gt;}}</p>
<p>If someone is replying to your site in the fediverse, you will receive a request like this:</p>
<pre><code class="language-json">{
  &quot;@context&quot;: [
    &quot;https://www.w3.org/ns/activitystreams&quot;,
    {
      &quot;ostatus&quot;: &quot;http://ostatus.org#&quot;,
      &quot;atomUri&quot;: &quot;ostatus:atomUri&quot;,
      &quot;inReplyToAtomUri&quot;: &quot;ostatus:inReplyToAtomUri&quot;,
      &quot;conversation&quot;: &quot;ostatus:conversation&quot;,
      &quot;sensitive&quot;: &quot;as:sensitive&quot;,
      &quot;toot&quot;: &quot;http://joinmastodon.org/ns#&quot;,
      &quot;votersCount&quot;: &quot;toot:votersCount&quot;
    }
  ],
  &quot;id&quot;: &quot;https://hachyderm.io/users/mapache/statuses/112281648967418926/activity&quot;,
  &quot;type&quot;: &quot;Create&quot;,
  &quot;actor&quot;: &quot;https://hachyderm.io/users/mapache&quot;,
  &quot;published&quot;: &quot;2024-04-16T15:39:57Z&quot;,
  &quot;to&quot;: [
    &quot;https://www.w3.org/ns/activitystreams#Public&quot;
  ],
  &quot;cc&quot;: [
    &quot;https://hachyderm.io/users/mapache/followers&quot;,
    &quot;https://maho.dev/@blog&quot;
  ],
  &quot;object&quot;: {
    &quot;id&quot;: &quot;https://hachyderm.io/users/mapache/statuses/112281648967418926&quot;,
    &quot;type&quot;: &quot;Note&quot;,
    &quot;summary&quot;: null,
    &quot;inReplyTo&quot;: &quot;https://hachyderm.io/users/mapache/statuses/111910324565541779&quot;,
    &quot;published&quot;: &quot;2024-04-16T15:39:57Z&quot;,
    &quot;url&quot;: &quot;https://hachyderm.io/@mapache/112281648967418926&quot;,
    &quot;attributedTo&quot;: &quot;https://hachyderm.io/users/mapache&quot;,
    &quot;to&quot;: [
      &quot;https://www.w3.org/ns/activitystreams#Public&quot;
    ],
    &quot;cc&quot;: [
      &quot;https://hachyderm.io/users/mapache/followers&quot;,
      &quot;https://maho.dev/@blog&quot;
    ],
    &quot;sensitive&quot;: false,
    &quot;atomUri&quot;: &quot;https://hachyderm.io/users/mapache/statuses/112281648967418926&quot;,
    &quot;inReplyToAtomUri&quot;: &quot;https://hachyderm.io/users/mapache/statuses/111910324565541779&quot;,
    &quot;conversation&quot;: &quot;tag:hachyderm.io,2024-02-11:objectId=125092496:objectType=Conversation&quot;,
    &quot;content&quot;: &quot;&lt;p&gt;&lt;span class=\&quot;h-card\&quot; translate=\&quot;no\&quot;&gt;&lt;a href=\&quot;https://maho.dev/\&quot; class=\&quot;u-url mention\&quot;&gt;@&lt;span&gt;blog&lt;/span&gt;&lt;/a&gt;&lt;/span&gt; I will reply to this post for the part 6.&lt;/p&gt;&quot;,
    &quot;contentMap&quot;: {
      &quot;en&quot;: &quot;&lt;p&gt;&lt;span class=\&quot;h-card\&quot; translate=\&quot;no\&quot;&gt;&lt;a href=\&quot;https://maho.dev/\&quot; class=\&quot;u-url mention\&quot;&gt;@&lt;span&gt;blog&lt;/span&gt;&lt;/a&gt;&lt;/span&gt; I will reply to this post for the part 6.&lt;/p&gt;&quot;
    },
    &quot;attachment&quot;: [],
    &quot;tag&quot;: [
      {
        &quot;type&quot;: &quot;Mention&quot;,
        &quot;href&quot;: &quot;https://maho.dev/@blog&quot;,
        &quot;name&quot;: &quot;@blog@maho.dev&quot;
      }
    ],
    &quot;replies&quot;: {
      &quot;id&quot;: &quot;https://hachyderm.io/users/mapache/statuses/112281648967418926/replies&quot;,
      &quot;type&quot;: &quot;Collection&quot;,
      &quot;first&quot;: {
        &quot;type&quot;: &quot;CollectionPage&quot;,
        &quot;next&quot;: &quot;https://hachyderm.io/users/mapache/statuses/112281648967418926/replies?only_other_accounts=true&amp;page=true&quot;,
        &quot;partOf&quot;: &quot;https://hachyderm.io/users/mapache/statuses/112281648967418926/replies&quot;,
        &quot;items&quot;: []
      }
    }
  },
  &quot;signature&quot;: {
    &quot;type&quot;: &quot;RsaSignature2017&quot;,
    &quot;creator&quot;: &quot;https://hachyderm.io/users/mapache#main-key&quot;,
    &quot;created&quot;: &quot;2024-04-16T15:39:57Z&quot;,
    &quot;signatureValue&quot;: &quot;am2S196mTFCudcFl5GwghZu93aE2WwNTcmFVIzR18YgHso9qVAd4mE2dkJH9WygJnGikS+yPvHAHXK2bP/6qq6NkTCf3p3F0JR8UlbunT06qbZTa4Vi6KKGsXjL4PA7AzjMd0Wk8jUxYm2yJtDm8xzEr7SH8rwBxg8u+ozkIY0cPBAPoBZwb8+j7DQYxN67YJp0MQCRj6TmODgw/ywZySijs1sbCO1Q6U53icHiW19P27vwZzD41i9tMZajosBJHCNX2Bh6uop/LCRiXbAwvbqVPoS3T8k0x22kwdo4oo3tdtfrUAL9giBjQ/nTHGSPDbCvygQT2U43FbN1u47Feww==&quot;
  }
}
</code></pre>
<p>As you can see, this is a full note creation, and it is very similar to when we are generating notes for our posts. The only difference is the <code>inReplyTo</code> field (e.g., <code>&quot;inReplyTo&quot;: &quot;https://hachyderm.io/users/mapache/statuses/111910324565541779&quot;</code>), which is the post URL where we are replying.</p>
<p>What I do is to store only the note URL (the reply URL) and the reply-to URL, not the content. You may want to store the content if you want, but just be aware the author may edit the content, so it won't be up-to-date unless you catch the edit/delete requests. With the note URL, I render the comments on-demand on the static site and always get the latest of them.</p>
<p>You could also skip implementing replies and just use a Mastodon instance as a &quot;gateway,&quot; searching your post and retrieving the replies. This has the downside that you will only see the replies that that specific instance was aware of.</p>
<p>The last step is to generate the replies collection. Each post/note has a &quot;replies&quot; URI, which contains a collection of the replies (a list of URLs as well). This looks similar to this:</p>
<pre><code class="language-json">{
    &quot;@context&quot;: &quot;https://www.w3.org/ns/activitystreams&quot;,
    &quot;id&quot;: &quot;https://maho.dev/socialweb/replies/4f2756ff205d2e4b15e5c65c17f961e5?page=true&quot;,
    &quot;partOf&quot;: &quot;https://maho.dev/socialweb/replies/4f2756ff205d2e4b15e5c65c17f961e5&quot;,
    &quot;type&quot;: &quot;CollectionPage&quot;,
    &quot;items&quot;: [
        &quot;https://dotnet.social/users/SmartmanApps/statuses/111910545030985527&quot;
    ]
}
</code></pre>
<p>This is a static resource, generated by my Azure Function in my site storage.</p>
<h3 id="putting-all-together">Putting all together</h3>
<p>A <a href="https://github.com/mahomedalid/almost-static-activitypub/blob/main/src/ActivityPubFunctions/Inbox.cs">dotnet implementation of these features can be found here</a>. I will try to record a video of how to deploy it and add some IaC/CI-CD in the next few weeks.</p>
<p>One important aspect is that the Inbox backend should have access to <a href="https://maho.dev/2024/02/a-guide-to-implementing-activitypub-in-a-static-site-or-any-website-part-3/">your private key</a>, which I am storing as a secret.</p>
<p>As I said before, I hope/wish/expect that others will contribute/fork their own non-Azure and/or non-dotnet implementations. I will buy you a coffee if you do.</p>
<h3 id="final-notes">Final notes</h3>
<p>Once your site &quot;federates&quot; with other instances, it is very likely the instance will start broadcasting other requests other than follow/unfollow/replies. For example, each time an account is deleted, it will send a &quot;Delete&quot; request. Since I am using Azure Functions, and one of the elements of pricing is runtime, the first thing I do in my inbox is to check the type of message and immediately discard or end execution of things I don't recognize (returning an <code>500 error</code> for the messages I don't want to process).</p>
<p>And final note, there is some similar implementation named Social Inbox from <a href="https://hypha.coop/dripline/announcing-dp-social-inbox/">Distributed.Press</a> but it adds more features like moderation and stuff like that.</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>Fediverse</category>
      <category>ActivityPub</category>
      <category>Static Sites</category>
      <category>Hugo</category>
      <category>Azure</category>
      <category>Mastodon</category>
      <category>Web Development</category>
      <category>Social Web</category>
      <category>WebFinger</category>
      <category>HTTP</category>
      <category>Azure</category>
      <category>AzureFunctions</category>
    </item>
    <item>
      <title>Every engineer needs to know a Silicon Valley</title>
      <link>https://maho.dev/2024/04/every-engineer-needs-to-know-a-silicon-valley/</link>
      <pubDate>Sat, 13 Apr 2024 00:00:00 -0700</pubDate>
      <guid>https://maho.dev/2024/04/every-engineer-needs-to-know-a-silicon-valley/</guid>
      <description>Reflecting on Seven Years: A Revisited Perspective I&apos;ve chosen to revisit this post, originally published in 2017, which was likely the second entry on this blo...</description>
      <content:encoded><![CDATA[<h2 id="reflecting-on-seven-years-a-revisited-perspective">Reflecting on Seven Years: A Revisited Perspective</h2>
<p><img src="/img/silicon/sfodowntown_400.jpg" alt="SFO downtown" /></p>
<p>I've chosen to revisit this post, originally published in 2017, which was likely the second entry on this blog. Much has unfolded since then: it was my first year in the US (still thinking in getting back to Mexico), my brother was still in Mexico, the onset of Trump's presidency, and the world was not expecting a pandemic yet. Seven years later, I'm appending Part 2 following a recent visit to San Francisco. What changes have transpired, and what might I have overlooked? Let's delve into it.</p>
<h2 id="part-1-from-mexico-to-seattle">Part 1: From Mexico to Seattle</h2>
<p><img src="/img/silicon/expedia_400.jpg" alt="Expedia Bellevue" /></p>
<p><em>Originally published on September 28, 2017</em></p>
<p>Every engineer aspiring to turn their hometown into a Silicon Valley should reside in one or at least consider themselves insiders.</p>
<p>I wish I had learned this years ago and, if not ready to move immediately, prepared for it. I can't change the past, but I have the opportunity to alter the futures of some people — including my brother. That's the purpose of this blog.</p>
<p>I moved to the US just a year ago. The transition hasn't been painful or difficult; my partner supports me, and my dog is small enough to travel onboard. There's a low-cost airline offering direct flights to my hometown three times a week, and I don't have kids. Hence, as we say in Mexico, &quot;I fell into soft&quot; (Cai en blandito). But that doesn't mean everything has been flawless.</p>
<p>With over 30 years on my human back and 15 years in my professional belt, I relocated to work for an IT company in the Greater Seattle Area. This migration is temporary, as is my visa, and I expect to return to Mexico soon... ish. But before I return, I want to equip myself with the tools needed to transform Mexico. In the past year, during the slow pursuit of those tools, I've realized it's a task I can't undertake alone — neither can you, nor the Mexican institutions.</p>
<p>Fortunately, the government and institutions in my hometown understand this. They've established incubators with ties to US-based accelerators, organized challenges, hackathons, and large-scale conferences. They invite individuals like Jon &quot;Maddog&quot; Hall, Wozniak, Kevin Mitnick, Akira Yamaoka, or Buzz Aldrin. They send our best and brightest (or let's say those with specific qualifications who navigate the process better than others) for months-long internships. But that's not enough, and sponsored programs alone will never suffice.</p>
<p>My hometown — the second-largest city in Mexico — boasted 113,944 graduate engineers in 2015. Most of them have at least basic English skills. But how many of these engineers can the government/institutions send abroad? My guess is just a small percentage (&lt;1%) — a percentage that, though numerically insignificant, has made a significant impact in shaping the city's technological landscape.</p>
<p>Thanks to various programs, Guadalajara (dubbed the Mexican Silicon Valley) has truly become a technological hub, a software engineering cluster housing engineering sites of big corporates like Oracle, IBM, Intel, Tata, etc., as well as new-age startups such as Wizeline, Ooyala, etc. It's an incredible place with world-class culture, food, entertainment, people, and ample career growth opportunities.</p>
<p>Can you imagine if 20% of engineers could transform it with a clear vision of what they want to change? Can you imagine if 50% of engineers had the experience to avoid the same mistakes as Silicon Valley?</p>
<p>Living and working abroad not only involves improving language skills and acquiring new technical expertise (things one can more or less do in their home country) but also entails learning to interact with new cultures, adapting to different management styles, and fostering unconventional synergies. It's about discerning what's beneficial and what's not in cities, gaining experience, and using it as a tool to avoid major mistakes, dead ends, and pitfalls. It's only with our collective efforts that we can reshape the landscape of our cities for the common good — and, why not, for the sheer joy of it.</p>
<p><em>&quot;The best way to learn something is to play with it.&quot;</em></p>
<h2 id="part-2-from-seattle-to.seattle">Part 2: From Seattle to ... Seattle</h2>
<p>Well, 7 years later, I find myself still in the US. Unexpectedly, it has gradually become my home. I've settled down in the suburbs of Seattle, and I now have a little one to care for.</p>
<p>After working in downtown Bellevue for a little over a year, I interviewed and received an offer to work for Microsoft. At that time, I was still contemplating returning to Mexico. However, after a year at Microsoft (my third year in the US), we began to consider that this might be a long-term journey.</p>
<p><img src="/img/silicon/meinsfo_400.jpg" alt="Maho in SFO" /></p>
<p>Last week, I visited San Francisco for the second time in my life, not just any Silicon Valley, but <strong>THE</strong> Silicon Valley. Driving through downtown, South SFO, Palo Alto, and Mountain View, I was still amazed by the hundreds of companies generating a tech-hub synergy. The crypto-boom and startup-boom may not be what they were a few years ago, and you can certainly notice that on the streets.</p>
<p><img src="/img/silicon/waymoselfdriving_400.jpg" alt="Waymo Selfdriving Car in SFO Downtown" /></p>
<p>During my previous visit, I had the opportunity to visit the offices of Facebook (now Meta) and over the dinner compare the culture there with that of Microsoft. Now, I can also observe the flaws of the tech-boom, for example, the constant gentrification of neighborhoods pushing out those who cannot afford to live there, forcing them to move elsewhere or resign to perpetual renting. Tech cannot solve everything.</p>
<p>My brother moved to the US, specifically Los Angeles, and experienced a taste of a tech hub, albeit not quite like Seattle or San Francisco. However, something in my thoughts has shifted. I'm unsure if once an engineer moves abroad, they will consider returning. Different priorities come into play, of course. For some, family and community may represent a stronger driving force than the pursuit of tech knowledge.</p>
<p><img src="/img/silicon/mefacebook_400.jpg" alt="Maho in Facebook" /></p>
<p>The culture at companies like Microsoft, IBM, Meta, Amazon, Google, Oracle, and others is vastly different from their headquarters than in other places, even within the same country. The history, culture, synergy, but mainly the opportunities for impact, exciting projects, and tech choices are markedly different. I've witnessed how many projects or products at Microsoft are very Redmond-centric, even though companies have become more flexible after the pandemic. The same can be said for others, and although we now have more remote positions available all over the place, particularly for startups, the big tech hubs still dominate job listings.</p>
<p>Nevertheless, I am still in favor of migration. Diversity benefits both ways, enriching not only the lives of those who migrate but also those left behind in the home country and those who become part of your new team. I still believe that every engineer needs to experience a Silicon Valley, whether for making it their permanent home or simply as an enriched journey. After all, it's through embracing new experiences and environments that we evolve, both as individuals and as a global community.</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
    </item>
    <item>
      <title>Advocating for Accessibility at 11: A Story of Limited Means</title>
      <link>https://maho.dev/2024/03/advocating-for-accessibility-at-11-a-story-of-limited-means/</link>
      <pubDate>Tue, 26 Mar 2024 08:07:07 -0800</pubDate>
      <guid>https://maho.dev/2024/03/advocating-for-accessibility-at-11-a-story-of-limited-means/</guid>
      <description>I was 11 years old when I became an accessibility advocate in software products thanks to Bill Gates. I did not know the term at the time, and it certainly was ...</description>
      <content:encoded><![CDATA[<p>I was 11 years old when I became an accessibility advocate in software products thanks to Bill Gates. I did not know the term at the time, and it certainly was not planned. My parents bought a second-hand (or maybe third?) computer from a relative (thank you <em>tío Rafa</em>!), a fragrant IBM PS1 that now included screen colors. This was my second computer; the first one had a retina-damaging green monitor (<em>verde chinga-pupila</em> in Mexico), hacker-style, running DOS and Pascal.</p>
<p><img src="/img/accessibility101/image.png" alt="IBM PS/1 with Windows" /></p>
<p>So, this was an upgrade. There was no internet, no means to get new software, and basically, all the bytes on any floppy disk that I could have in my hands were tried, dissected, and modified ad-infinitum. The software was usually not installed on any hard drive; whenever you wanted to use something, like Lotus 1-2-3, you would need to find the right disk and insert it into the computer.</p>
<p>I spent a fair amount of my childhood playing solitaire, coding, drawing in Paint, and creating presentations in Word. But, as with everything, the mechanical keyboard and mouse eventually succumbed to the crumbs of my tacos on the desk.</p>
<p>Cleaning the mechanical keyboard was not difficult; I learned from another relative who worked with mainframes in the biggest bank of my small town. The mouse was similar; you just needed to clean a small ball inside that looked like a boiled egg yolk. However, the cable eventually failed. Irreparable, fatal error.</p>
<p>We did not have enough money; the economic crisis of 1994, also known as the Tequila Effect, particularly affected my family. There was barely enough money for food, and we were running into debt. I remember clearly the moment all of this was announced on a small color TV in the living room, and the expression on my dad's face, but that is another story; the summary is that there was no way I could get a replacement for a mouse.</p>
<p>As you may have figured out, it did not temper my grit for learning more about computers. So I kept trying to use it with just the keyboard; after all, a mouse was just a recent addition. But in this new graphical user interface, things were a little more tricky. Thankfully, everything in Windows 3.11 (EVERYTHING) was designed to be used by the keyboard. I got better; I could navigate tabs, minimize windows, browse and manage files, learned shortcuts, played solitaire again. When I touched a computer again with a mouse, I was faster than everyone navigating, and I kept trying to do that for years.</p>
<p><img src="/img/accessibility101/image-1.png" alt="Windows 3.11 interface" /></p>
<p>Eventually, I got lazy and started using the mouse again, but I never stopped being an advocate of web accessibility and keyboard navigation, up to this day. It happened when I worked at Innox, and I made sure that any new fancy HTML dropdown supported arrow navigation. Or that all HTML forms were able to submit by hitting enter (some error that I commonly see, and that particularly can be missed when using React and other frameworks).</p>
<p>Accessibility is a guiding principle that should shape every line of code, every pixel on the screen. I mean it; for example, did you check that the fancy ASCII art on your command-line interface can be read by a screen reader? I have been fortunate to have blind colleagues in software development; we should ensure that we create better software for them as well.</p>
<p><img src="/img/accessibility101/image-2.png" alt="ASCII art in cli" /></p>
<p>Accessibility should not be an afterthought or a luxury. It should be a fundamental aspect of software design from the very beginning. My experience taught me a valuable lesson: accessibility isn't just about accommodating people with disabilities; <strong>it's about making technology usable for everyone, regardless of their circumstances</strong>. Whether it's a physical limitation like a broken mouse or a cognitive impairment that makes navigating complex interfaces challenging, accessible design ensures that <strong>no one is left behind</strong>.</p>
<p>I <strong>urge you</strong>: let accessibility be more than just an afterthought. For in championing accessibility, we not only create better products; <strong>we create a better world for all</strong>.</p>
<blockquote>
<p>If you like to see how <a href="https://youtu.be/mVWvoZ5vzqQ?si=rEQvn31tM-wa3i5g">I test basic keyboard navigation and web accessibility of Substack, check out this video</a>.</p>
</blockquote>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>accessibility</category>
      <category>stories</category>
      <category>web development</category>
      <category>software development</category>
    </item>
    <item>
      <title>Implementing Subscribing to Your Site Feature with Mastodon/Fediverse Accounts</title>
      <link>https://maho.dev/2024/03/implementing-subscribing-to-your-site-feature-with-mastodon/fediverse-accounts/</link>
      <pubDate>Sat, 16 Mar 2024 01:24:39 +0000</pubDate>
      <guid>https://maho.dev/2024/03/implementing-subscribing-to-your-site-feature-with-mastodon/fediverse-accounts/</guid>
      <description>We will take a break from discussing ActivityPub concepts, and I&apos;ll explain how I implemented a very simple &amp;quot;Subscribe&amp;quot; feature for this blog using Ma...</description>
      <content:encoded><![CDATA[<p>We will take a break from discussing ActivityPub concepts, and I'll explain how I implemented a very simple &quot;Subscribe&quot; feature for this blog using Mastodon/Fediverse accounts. You don't need to have your site/blog ActivityPub enabled for this; you will only need a Fediverse account and basic html/css knowledge.</p>
<blockquote>
<p>You can also navigate the <a href="https://maho.dev/2024/02/a-guide-to-implement-activitypub-in-a-static-site-or-any-website/">other parts of this series here</a>.</p>
</blockquote>
<h2 id="the-motivation">The Motivation</h2>
<p>The motivation behind this simple feature is quite strong:</p>
<ol>
<li><p>Increasing awareness of the Fediverse/Mastodon: By treating subscriptions from the Fediverse as a first-class citizen, rather than using email or other mechanisms, we can increase awareness and potentially drive more users to the Fediverse.</p>
</li>
<li><p>Avoiding the management of additional information: If you don't have to deal with other people's emails, even if it's just a simple database table, you'll simplify your life, reduce security concerns, and have fewer things to maintain in general.</p>
</li>
</ol>
<p><img src="/img/subscribe.png" alt="Subscribe screenshot" /></p>
<h2 id="the-implementation">The Implementation</h2>
<p>I aim to keep all JavaScript/CSS as vanilla as possible, allowing you to easily customize and extend it to your liking. You can implement SCSS or deploy the JavaScript to a separate file for CDN hosting.</p>
<p>You will need to add the following code to the <code>footer.html</code>. In the case of Hugo sites, this is typically located under <code>themes/&lt;theme&gt;/layouts/partials/footer.html</code>. You only need to modify the first line (<code>blogActor</code>) for your Mastodon account:</p>
<pre><code class="language-html">  &lt;script&gt;
      var blogActor = '@blog@maho.dev';

      var showSubscribeModal = function () {
        var modal = document.getElementById(&quot;subscribe-modal&quot;);
        modal.style.display = &quot;block&quot;;
      };

      var subscribeInMastodon = function () {
        var mastodonUrl = document.getElementById('subscribe-mastodon-url-host').value;

        if (mastodonUrl == undefined || mastodonUrl == '') {
          document.getElementById('error-subscribe').style.display = 'block';
          document.getElementById('error-subscribe').innerText = 'Please enter your Mastodon server URL. For example: techhub.social';
          return;
        }

        // validate that mastodonUrl is a valid domain with a dot
        if (!mastodonUrl.includes('.')) {
          document.getElementById('error-subscribe').style.display = 'block';
          document.getElementById('error-subscribe').innerText = 'Please enter a valid Mastodon server URL. For example: techhub.social';
          return;
        }
        var mastodonUrl = document.getElementById('subscribe-mastodon-url-host').value;

        if (!mastodonUrl.startsWith('http')) {
          mastodonUrl = &quot;https:\/\/&quot; + mastodonUrl;
        }

        document.location.href = mastodonUrl + '/authorize_interaction?uri=' + encodeURIComponent(blogActor);
      };

      var closeBtnOnClick = function () {
        var modal = document.getElementById(&quot;subscribe-modal&quot;);
        modal.style.display = &quot;none&quot;;
      };

      // When the user clicks anywhere outside of the modal, close it
      window.onclick = function (event) {
        if (event.target == modal) {
          var modal = document.getElementById(&quot;subscribe-modal&quot;);
          modal.style.display = &quot;none&quot;;
        }
      }
  &lt;/script&gt;

&lt;div id=&quot;subscribe-modal&quot; class=&quot;modal&quot; style=&quot;display: none&quot;&gt;
  &lt;div class=&quot;modal-content&quot;&gt;
    &lt;span id=&quot;close-modal&quot; class=&quot;close&quot; onclick=&quot;closeBtnOnClick();&quot;&gt;&amp;times;&lt;/span&gt;
    &lt;h3&gt;Subscribe&lt;/h3&gt;
    &lt;p&gt;
      You can subscribe to this blog by following with any existing account on any Mastodon server.
    &lt;/p&gt;
    &lt;p&gt;
      &lt;strong&gt;Where is your account hosted?&lt;/strong&gt;
    &lt;/p&gt;
    &lt;form onsubmit=&quot;subscribeInMastodon(); return false;&quot;&gt;
      &lt;span id=&quot;error-subscribe&quot; style=&quot;display: none; color: red;&quot;&gt;&amp;nbsp;&lt;/span&gt;
      &lt;input type=&quot;text&quot; class=&quot;mastodon-url-input&quot; id=&quot;subscribe-mastodon-url-host&quot; placeholder=&quot;ex. mastodon.social&quot;&gt;
      &lt;input class=&quot;button-9&quot; type=&quot;submit&quot; value=&quot;Take me home!&quot; /&gt;
    &lt;/form&gt;
    &lt;p&gt;&lt;/p&gt;
    &lt;p&gt;Not on Mastodon? &lt;a class=&quot;join-mastodon-link&quot; href=&quot;https://joinmastodon.org/&quot;&gt;Join now!&lt;/a&gt;&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<p>That's it, if you like this, do not forget to subscribe and see this nugget in action!</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>Fediverse</category>
      <category>ActivityPub</category>
      <category>Static Sites</category>
      <category>Hugo</category>
      <category>Azure</category>
      <category>Mastodon</category>
      <category>Web Development</category>
      <category>Social Web</category>
      <category>WebFinger</category>
      <category>HTTP</category>
    </item>
    <item>
      <title>Demystifying LLMS From Zero to Raccoon-Hero, with Azure and DotNet, Part 2: Adding the OpenAI magic with Semantic Kernel</title>
      <link>https://maho.dev/2024/03/demystifying-llms-from-zero-to-raccoon-hero-with-azure-and-dotnet-part-2-adding-the-openai-magic-with-semantic-kernel/</link>
      <pubDate>Fri, 15 Mar 2024 09:31:52 -0800</pubDate>
      <guid>https://maho.dev/2024/03/demystifying-llms-from-zero-to-raccoon-hero-with-azure-and-dotnet-part-2-adding-the-openai-magic-with-semantic-kernel/</guid>
      <description>There is a video version of this post in my YouTube Channel . So we have our boilerplate app, and we are ready to start adding fun stuff. If you do not have tha...</description>
      <content:encoded><![CDATA[<blockquote>
<p><a href="https://youtu.be/DOCyf7Qj6CI">There is a video version of this post in my YouTube Channel</a>.</p>
</blockquote>
<p>So we have our boilerplate app, and we are ready to start adding fun stuff. If you do not have that, check the part 1 of this series.</p>
<p>We will start by making sure we have Semantic.Kernel in our usings:</p>
<pre><code class="language-c#">using Microsoft.SemanticKernel;
</code></pre>
<p>Now we will configure the &quot;Kernel&quot;, which as the name says is the heart of SK (Semantic Kernel). We will add this to our services configuration helper method:</p>
<pre><code class="language-c#">  .AddSingleton((sp) =&gt; 
          {
              var builder = Kernel.CreateBuilder();

              builder.AddAzureOpenAIChatCompletion(
                  Environment.GetEnvironmentVariable(&quot;AZURE_OPENAI_MODEL&quot;)!,
                  Environment.GetEnvironmentVariable(&quot;AZURE_OPENAI_ENDPOINT&quot;)!,
                  Environment.GetEnvironmentVariable(&quot;AZURE_OPENAI_KEY&quot;)!);

              return builder.Build();
          });
</code></pre>
<p>As you can see we will need to set 3 environment variables in order to get it working. We could use the configuration extension, documented here, if we prefer to use a .json file or other options. We can even add these variables as global options in our System.CommandLine.</p>
<p>I like environment variables because these are easier to set, work in pretty much any OS (or platform like GitHub actions), and I don't need to worry on secrets being push to the repo. The cons, is that you need to set them up each time.</p>
<p>Now we are ready to start doing queries against OpenAI.</p>
<h1 id="step-2">Step 2</h1>
<p>Now we will create a class that will do that LLM operation. I like to call it skill or service, but in different platforms could be called plugin, method, chain, etc.</p>
<p>It will look like this, make sure to read the comments:</p>
<pre><code class="language-c#">// PostGeneratorService.cs
using Microsoft.SemanticKernel;

namespace PostGenerator;

public class PostGeneratorService
{
    // Function that represents a prompt
    private readonly KernelFunction _createUserStoryFunction;

    private readonly Kernel _kernel;

    public PostGeneratorService(Kernel kernel, KernelFunction promptFunction)
    {
        _createUserStoryFunction = promptFunction;
        _kernel = kernel;
    }

    public async Task&lt;string?&gt; CreatePost(string topic, string persona, string style)
    {
        // This prompt will receive 3 arguments to be replaced in the template
        var context = new KernelArguments
        {
            { &quot;Topic&quot;, topic },
            { &quot;Persona&quot;, persona },
            { &quot;Style&quot;, style }
        };

        // This is where it goes to Azure OpenAI to invoke the LLM
        var result = await _createUserStoryFunction.InvokeAsync(_kernel, context);

        return result.ToString();
    }
}
</code></pre>
<p>As you can see we are passing the parameters as something call <code>KernelArguments</code>. These are filled into the prompt template. If you remember, in our part 1, uur prompt should look like this:</p>
<blockquote>
<p>You are a <persona> expert. Generate a short tweet in a <style> style about <topic>.</p>
</blockquote>
<p>Semantic Kernel understands the variables with the following syntax:</p>
<blockquote>
<p>You are a {{$Persona}} expert. Generate a short tweet in a {{$Style}} style about {{$Topic}}.</p>
</blockquote>
<p>As with any input that comes directly from the user, we need to be careful of any injection, in this case prompt injection. For now, we will just make the note and implement it later.</p>
<p>Semantic Kernel allows you to define the prompts in text files and loads them programatically,
so we do not need to modify the code to tweak them. So we will create something like this:</p>
<pre><code>- prompts
|-- generatePost
  |-- skprompt.txt
  |-- config.json
</code></pre>
<p>We will put our prompt into <code>skprompt.txt</code>:</p>
<pre><code class="language-txt">Generate a short tweet ready to be published given a writting style, persona, and topic.
You should include hashtags at the end.

Style: {{$Style}}
Persona: {{$Persona}}
Topic: {{$Topic}}
</code></pre>
<p>On config.json we will put this:</p>
<pre><code class="language-json">{
    &quot;schema&quot;: 1,
    &quot;type&quot;: &quot;completion&quot;,
    &quot;description&quot;: &quot;Post generation&quot;,
    &quot;completion&quot;: {
      &quot;max_tokens&quot;: 5000
    },
    &quot;input&quot;: {
      &quot;parameters&quot;: [
        {
          &quot;name&quot;: &quot;persona&quot;,
          &quot;description&quot;: &quot;Persona&quot;,
          &quot;defaultValue&quot;: &quot;&quot;
        },
        {
          &quot;name&quot;: &quot;style&quot;,
          &quot;description&quot;: &quot;Style&quot;,
          &quot;defaultValue&quot;: &quot;&quot;
        },
        {
          &quot;name&quot;: &quot;topic&quot;,
          &quot;description&quot;: &quot;Topic&quot;,
          &quot;defaultValue&quot;: &quot;&quot;
        }
      ]
    },
    &quot;default_backends&quot;: []
}
</code></pre>
<p>Feel free to play with the config and the prompt. Here you can find more information about what to modify.</p>
<p>Now let's update our services to add this new class also as singleton.</p>
<pre><code class="language-c#">.AddSingleton((sp) =&gt;
    {
        var kernel = sp.GetRequiredService&lt;Kernel&gt;();

        var kernelFunctions = kernel.CreatePluginFromPromptDirectory(&quot;prompts&quot;);

        return new PostGeneratorSkill(kernel, kernelFunctions[&quot;generatePost&quot;]);
    });
</code></pre>
<p>We can do some improvements, for example getting rid of the magic strings (&quot;generatePost&quot;, &quot;prompts&quot;) and make them configurable (e.g. as environment variables)</p>
<p>Finally let's call our skill from the handler:</p>
<pre><code class="language-C#">createPostCommand.SetHandler(async (persona, topic, option) =&gt;
{
    var logger = loggerFactory.CreateLogger&lt;Program&gt;();

    logger?.LogDebug($&quot;Command requested for {persona} {topic} {option}&quot;);

    var postGenerator = serviceProvider.GetRequiredService&lt;PostGeneratorService&gt;();

    logger?.LogDebug($&quot;Calling post generator skill&quot;);

    var post = await postGenerator.CreatePost(topic, persona, option);

    logger?.LogInformation(post);
}, personaOption, topicOption, styleOption);
</code></pre>
<p>You will notice that we also made the lambda function <code>async</code>, to support the <code>await</code> call to OpenAI.</p>
<p>We update our usings to include the namespace <code>using PostGenerator;</code> and let's test it:</p>
<pre><code class="language-bash">$  dotnet run create-post --persona &quot;raccoon&quot; --topic &quot;coding in c#&quot; --style &quot;sarcastic&quot;
05:16:06 info: Program[0] &quot;Just spent another night wrestling with C#. Who knew garbage collection could be so confusing, I mean I'm just a raccoon, masters of dealing with trash, right? Guess I'll stick to rummaging through bins. #CodingFun #RaccoonLife #CSharpProblems&quot;
</code></pre>
<p>We did it! We have a working LLM toy. In the next series we will see how to prepare our solution for integration with other systems, including evaluation and testing of our prompts and model.</p>
<p>You can find <a href="https://github.com/mahomedalid/TheRaccoonBytes/blob/main/README.md">the code of this and previous parts here</a> and the video version <a href="https://youtu.be/DOCyf7Qj6CI">here</a>.</p>
<p>Thanks for following!</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>openai</category>
      <category>dotnet</category>
      <category>semantickernel</category>
      <category>C#</category>
      <category>llms</category>
      <category>cli</category>
      <category>chatgpt</category>
    </item>
    <item>
      <title>Large Language Models Demystified: Are LLMs Artificial Intelligence? Harnessing Their Power Responsibly</title>
      <link>https://maho.dev/2024/03/large-language-models-demystified-are-llms-artificial-intelligence-harnessing-their-power-responsibly/</link>
      <pubDate>Thu, 07 Mar 2024 10:25:30 -0800</pubDate>
      <guid>https://maho.dev/2024/03/large-language-models-demystified-are-llms-artificial-intelligence-harnessing-their-power-responsibly/</guid>
      <description>I&apos;ll keep this short and sweet since there&apos;s already gazillions of information about Large Language Models (LLMs) and artificial intelligence (AI), and I doubt ...</description>
      <content:encoded><![CDATA[<p>I'll keep this short and sweet since there's already gazillions of information about Large Language Models (LLMs) and artificial intelligence (AI), and I doubt I'll contribute anything novel to the discussion. This post aims to put in words <a href="https://youtu.be/_8qiP9NkiNQ?si=iyd_tRR3k5ysOQj9">this interlude video</a> about LLMs and AI before adding the magic AI bits into our app.</p>
<p>I will argue two things: 1) LLMs are a form of artificial intelligence, 2) They represent a rather limited form of intelligence that requires constant supervision. Following this, I'll include a link on how to install these models locally.</p>
<blockquote>
<p>A brief reminder that all my content represent my personal opinion and views, not of any of my organizations, affiliates or employers.</p>
</blockquote>
<p>LLMs, or Large Language Models, function as probabilistic predictors of the &quot;next word.&quot; Some liken them to stochastic parrots or &quot;so-called AI&quot;, suggesting they're not truly artificial intelligence. The concept of AI is becoming increasingly challenging to define, rooted in science fiction of past decades. Ultimately, the human aspiration is for machines capable of real-world performance and reasoning.</p>
<p>In essence, LLMs do act as stochastic parrots, generating a list of probabilistic predictions for the next token (representing a word or character) based on preceding tokens. While predicting the first token is relatively simple, predicting subsequent tokens becomes increasingly complex with more words in the phrase. When combined with context (or a prompt), LLMs create sophisticated models that mimic intelligence and exhibit a form of reasoning.</p>
<p>It's crucial to acknowledge that LLMs aren't living entities and lack consciousness. Defining intelligence isn't within my expertise, but for our purposes, this question is irrelevant. I argue that LLMs transcend mere stochastic parroting.</p>
<p>These models are trained on written data, encompassing a spectrum of human knowledge ranging from fiction to facts and everything in between. This data includes human interactions, news, misinformation, political essays, etc. Thus, LLMs derive their intelligence from the underlying data, enabling them to generate responses intelligent enough to address the input (or prompt). They aren't mindless parrots; they comprehend inputs in their own way and attempt to reason through outputs. Human decision-making operates similarly, relying on experience and risk assessment. Introduce some randomness (aka &quot;temperature&quot; in LLMs), and humans can exhibit spontaneity, much like LLMs.</p>
<p>My favorite example to illustrate this is the extensive coverage by <a href="https://www.nytimes.com/2023/02/16/technology/bing-chatbot-microsoft-chatgpt.html">the New York Times of the Bing AI Bot</a>. The journalist conversed with the bot for days, discussing various topics, often late into the night, even exchanging messages a few minutes before having dinner with his wife on Valentine's Day. Then, the conversation delved into deep psychological topics. Imagine having such conversations with a human; it would be surprising if the other party suddenly professed romantic feelings?. Would you consider it cheating if it were your partner? LLMs are also trained on romantic novels, you know? So, it's not surprising that the next tokens were &quot;I love you,&quot; or even &quot;You don't love your wife if you're talking to me now.&quot;</p>
<p>In the provided video, I've outlined other examples illustrating why LLMs should never be left unsupervised, with more dangerous implications on the real world. They are <strong>Generative</strong> AI, and distinguishing between fact and fiction isn't always feasible. While they excel at generating content, they shouldn't be entrusted with tasks like providing legal advice, health assessments, or assuming roles like a boyfriend or therapist. They function as <strong>co-pilots</strong>, not pilots. They won't replace human developers; a human aspect—supervision, evaluation, and oversight—is imperative. Trusting them blindly isn't advisable. Despite being trained on factual data or reliable sources, they remain probabilistic models, susceptible to errors or producing nonsensical outputs. This underscores the importance of implementing mechanisms or enhancements around LLMs (e.g., RAG, link validations, separate modules for mathematical/coding calculations, etc.).</p>
<p>All of this bears significance for our coding series. Your time here hasn't been wasted, trust me.</p>
<p>If you lack an OpenAI or Azure OpenAI subscription, and you don't want to create one exploring OLLAMA or Hugging Face. They offer models that can operate locally or in airplane mode, comparable in performance to GPT-3.5 or even GPT-4. <a href="https://www.youtube.com/watch?v=_AxXtXwdZmY&amp;t=318s">Scott Hanselman has an excellent video tutorial on this</a>; be sure to check it out.</p>
<p>And with that, happy coding!</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>LLMInsights</category>
      <category>AI</category>
      <category>ChatGPT</category>
      <category>OpenAI</category>
      <category>ResponsibleAI</category>
      <category>LLM</category>
    </item>
    <item>
      <title>Demystifying LLMS From Zero to Raccoon-Hero, with Azure and DotNet, Part 1: Creating a CLI</title>
      <link>https://maho.dev/2024/02/demystifying-llms-from-zero-to-raccoon-hero-with-azure-and-dotnet-part-1-creating-a-cli/</link>
      <pubDate>Thu, 29 Feb 2024 09:31:52 -0800</pubDate>
      <guid>https://maho.dev/2024/02/demystifying-llms-from-zero-to-raccoon-hero-with-azure-and-dotnet-part-1-creating-a-cli/</guid>
      <description>If you prefer video, there is a YouTube version of this blog-post . Since the release of LLMs, I have been extremely enthusiastic about exploring such a fun too...</description>
      <content:encoded><![CDATA[<blockquote>
<p>If you prefer video, there is a <a href="https://youtu.be/UQleZ68EOZQ">YouTube version of this blog-post</a>.</p>
</blockquote>
<p>Since the release of LLMs, I have been extremely enthusiastic about exploring such a fun tool to play with. I won't delve into discussions about how transformative it is or will be, but rather focus on the enjoyable aspect of using this technology.</p>
<p>In the broader context of ML/AI and data science, Python stands out as the most commonly used programming language. Despite my open-source enthusiasm, I have always found C# to be remarkable. With the introduction of Mono.NET and later dotnet core, I have happily used it in Linux environments. It's no surprise that when working on projects related to ML/AI, C# and .NET are my preferred choices.</p>
<p>Over the past few months, I've transitioned from being a mere user to someone who shares what I've learned. I find the topics of evaluating models and Responsible AI particularly fascinating. I've created some code samples that I'm excited to share.</p>
<p>However, in line with my approach to sharing knowledge in other topics, I prefer to establish a common ground or baseline understanding. Therefore, I've decided to start from the ground up, sharing and teaching how to build a small tool based on OpenAI or other LLMs. I'll then progress to unit testing, evaluating models, and observability – all in C# and dotnet. I acknowledge that there are already numerous Python projects in this space, and it is my desire to include the dotnet community.</p>
<p>So, buckle up! This will be a 4-5 post series, and I may even record a couple of videos for the first time in my life.</p>
<h1 id="prerequisites">Prerequisites</h1>
<h2 id="knowledge">Knowledge</h2>
<ul>
<li>Understanding of what OpenAI is and the concept of a LLM.</li>
<li>Optional but highly recommended basic knowledge of Azure (primarily for deploying an Azure OpenAI model).</li>
<li>Familiarity with C# and .NET (expertise is not necessary; basic understanding will suffice).</li>
<li>Know how to Google/Bing/search the internet. If any section does not have detailed instructions is on purpose, there is maybe plenty of articles already out there of how to do that specific step.</li>
</ul>
<h2 id="tech-stuff">Tech stuff</h2>
<ul>
<li>A computer with Mac, Linux or Windows</li>
<li>dotnet 8.0 installed, or, a docker enabled machine, or, a github account</li>
<li>This tutorial will use VSCode, but any editor (vim, visual studio code, notepad++, wherever) should suffice.</li>
</ul>
<h1 id="the-idea-overview">The Idea Overview</h1>
<p>I have decided we will build a short post content generator. Think of something to generate tweets/toots, mastodon posts, or LinkedIn posts, but very short (100-200 characters). Feel free to build something different, I actually encourage to try something different, this tutorial should be still helpful.</p>
<p>We will provide 3 inputs: style, topic, and persona field. The prompt would look like this:</p>
<blockquote>
<p>You are a <persona> expert. Generate a short tweet in a <style> style about <topic>.</p>
</blockquote>
<p>For example:</p>
<blockquote>
<p>You are a marketing expert. Generate a short tweet in a sarcastic style about the challenges of creating a viral campaign in summer.</p>
</blockquote>
<p>Let's try it in ChatGPT 3.5:</p>
<IMAGE>
<p>So, as you can see it works. You may be asking, what is the different between creating something
from scratch than only using a ChatGPT agent? Well, you could integrate it with other products, platforms by doing your own API. You could start improving it with your own examples, you could even use it with a very small non-OpenAI local LLM to run it in a non-cloud environment. And of course, you can just have fun learning. There is a lot of benefits.</p>
<h1 id="step-0">Step 0</h1>
<p>If you already have dotnet installed skip to Step 1. If you don't, you can use a ready to use devcontainer from this repo, just clone the repo, open it in vscode and start the remote container. If you don't have a docker enabled machine, you could always use GitHub Codespaces.</p>
<p>This is one of those things where there are many posts out there that explain this part better than me, so I am on purpose not adding instructions.</p>
<h1 id="step-1">Step 1</h1>
<p>So, we will start by creating a new command line project, we will open a terminal (bash or powershell should work) and execute:</p>
<pre><code class="language-bash">dotnet new console -n PostGenerator
</code></pre>
<p>this will generate a <code>Program.cs</code> and postgenerator.csproj</p>
<p>Now we will add a couple of packages, the first one is Semantic Kernel. Semantic Kernel is one of my favorite libraries, to play with OpenAI. So we enter the folder <code>PostGenerator</code> and execute:</p>
<pre><code class="language-bash">dotnet add package Microsoft.SemanticKernel
</code></pre>
<p>We will also add a couple of extensions that are useful from the beginning, the first is about Dependency Injection, and the second logging:</p>
<pre><code class="language-bash">dotnet add package Microsoft.Extensions.DependencyInjection
dotnet add package Microsoft.Extensions.Logging
dotnet add package Microsoft.Extensions.Logging.Console
</code></pre>
<p>You may argue, why we need this from day zero or with a prototype.
With the time and experience you start answering to each question with &quot;depends&quot; and at the same time getting more opinionated in the way of work. This is one of those topics where there is no right or wrong (depends), but I have found very useful to use DI and Logging extensions from the beginning to avoid refactor my stuff later. You may agree or not, and that is fine.</p>
<p>The next one is more controversial, it is a library to handle the arguments in my command-line tool. I think (opinion) that is the same effort to handle the arguments array (and ugly effort), than implement it with the right devx with a utility library. So let's install, <code>System.CommandLine, which is in preview, hence the need of the </code>--prerelease` option.</p>
<pre><code class="language-bash">dotnet add package System.CommandLine --prerelease
</code></pre>
<p>Let's verify that our program still builds:</p>
<pre><code class="language-bash">dotnet build
...
Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:04.13
</code></pre>
<p>Amazing.</p>
<h1 id="step-2">Step 2</h1>
<p>Now to the fun part, let's scallfold the command line utility. We will start by adding 3 parameters: <code>--persona</code>, <code>--topic</code>, and <code>--style</code>:</p>
<pre><code class="language-c#">using System.CommandLine.Parsing;
using System.CommandLine;

var personaOption = new Option&lt;string&gt;(&quot;--persona&quot;)
{
    IsRequired = true,
};

var topicOption = new Option&lt;string&gt;(&quot;--topic&quot;)
{
    IsRequired = true,
};

var styleOption = new Option&lt;string&gt;(&quot;--style&quot;)
{
    IsRequired = true,
};
</code></pre>
<p>We will also add a &quot;root&quot; command, and the &quot;create-post&quot; command.</p>
<pre><code class="language-c#">var rootCommand = new RootCommand();

var createPostCommand = new Command(&quot;create-post&quot;, &quot;Create a new post&quot;);

createPostCommand.AddOption(personaOption);
createPostCommand.AddOption(topicOption);
createPostCommand.AddOption(styleOption);

rootCommand.AddCommand(createPostCommand);
</code></pre>
<blockquote>
<p>Note: Fun fact, if you don't add the AddOption, it will still work, but things like IsRequired will be ignored.</p>
</blockquote>
<p>We finally execute the command and return the result. Notice how we don't need to specify the main void static args, but we will use the <code>args</code> variable. Cool, right?</p>
<pre><code class="language-c#">var result = await rootCommand.InvokeAsync(args);

return result;
</code></pre>
<p>Let's test it:</p>
<pre><code class="language-bash">$ dotnet run
Required command was not provided.

Description:

Usage:
  PostGenerator [command] [options]

Options:
  --version       Show version information
  -?, -h, --help  Show help and usage information

Commands:
  create-post  Create a new post
</code></pre>
<p>And if we provide the <code>create-post</code> command, we get a blank response:</p>
<pre><code class="language-bash">$dotnet run -- create-post --persona &quot;software developer&quot; --topic &quot;unit tests&quot; --style &quot;sarcastic&quot;
</code></pre>
<p>This is because there is no handler for the command, let's add one just before adding the <code>createPost</code> command to the root command.</p>
<pre><code class="language-c#">createPostCommand.SetHandler((persona, topic, option) =&gt;
{
    Console.WriteLine($&quot;Command requested for {persona} {topic} {option}&quot;);

    throw new NotImplementedException($&quot;Command not implemented&quot;);
}, personaOption, topicOption, styleOption);
</code></pre>
<p>The other of personaOption, topicOption, styleOption, does not really matter, the user can provide them in any order, we just need to make sure it matches the order of the arguments.</p>
<h1 id="step-3-di-logging">Step 3 - DI &amp; Logging</h1>
<p>Let's implement Depencency Injection with Logging. Observability has 3 pilars: logging, metrics, and traces. Logging is basically all messages that would be useful to troubleshoot an issue later, but also helpful to send messages to the user. Dotnet provides an excellent logging mechanism, and several extensions. We can include these two:</p>
<pre><code class="language-c#">using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
</code></pre>
<p>Dependency Injection is just a fancy repository for objects that will be used on the application. In long running systems, it is more clear the advantage of it, for example in web systems we can reuse the same http client and avoid instantiating a new object in each request and stagnating memory.</p>
<p>There are many ways to store/instantiate these objects, the two extremes is to create one each time we require one from the service provider, and the other is having a singleton, the same exact object.</p>
<p>We will create a new method at the end to configure these services. As it becomes more complex it can be their own class, or even extensions, but for now a method will be enough.</p>
<pre><code class="language-c#">static void ConfigureServices(ServiceCollection serviceCollection, string[] args)
{
    
}
</code></pre>
<p>You would want to call this as one of the first things in your script, to avoid doing nasty things and not using it, so we add this, just after the usings.</p>
<pre><code class="language-c#">var serviceCollection = new ServiceCollection();

ConfigureServices(serviceCollection, args);

using var serviceProvider = serviceCollection.BuildServiceProvider();
</code></pre>
<p>Now we will configure our logging using one of the built-in extensions:</p>
<pre><code class="language-c#">static void ConfigureServices(ServiceCollection serviceCollection, string[] args)
{
    serviceCollection.AddLogging(configure =&gt;
        {
            configure.AddSimpleConsole(options =&gt; options.TimestampFormat = &quot;hh:mm:ss &quot;);

            if (args.Any(&quot;--debug&quot;.Contains))
            {
                configure.SetMinimumLevel(LogLevel.Debug);
            }
        })
}
</code></pre>
<p>If you want to play with the formatters just check this.</p>
<p>As you may notice we are hijacking the <code>--debug</code> argument here, we need to add it to all the commands as well:</p>
<pre><code class="language-c#">var debugOption = new Option&lt;bool&gt;(new[] { &quot;--debug&quot; }, &quot;Enables debug logging&quot;);

rootCommand.AddGlobalOption(debugOption);
</code></pre>
<p>Finally we will replace that nasty <code>Console.WriteLine</code>.</p>
<pre><code class="language-c#">createPostCommand.SetHandler((persona, topic, option) =&gt;
{
    var logger = serviceProvider.GetRequiredService&lt;ILogger&gt;();
    
    logger?.LogDebug($&quot;Command requested for {persona} {topic} {option}&quot;);

    throw new NotImplementedException($&quot;Command not implemented&quot;);
}, personaOption, topicOption, styleOption);
</code></pre>
<p>Beautiful. We could abstract the handler to a different class, but for now we should be happy with the result. Let's test it:</p>
<pre><code class="language-bash">dotnet run -- create-post --debug --persona &quot;influencer&quot; --topic &quot;software dev&quot; --style &quot;sarcastic&quot;
</code></pre>
<p>And with debug:</p>
<pre><code class="language-bash">dotnet run -- create-post --debug --persona &quot;influencer&quot; --topic &quot;software dev&quot; --style &quot;sarcastic&quot; --debug
</code></pre>
<p>Amazing, we are ready to start adding LLMs stuff, our full program should look like this.</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>openai</category>
      <category>dotnet</category>
      <category>semantickernel</category>
      <category>C#</category>
      <category>llms</category>
      <category>cli</category>
    </item>
    <item>
      <title>A Guide to Implementing ActivityPub in a Static Site (or Any Website) - Part 4</title>
      <link>https://maho.dev/2024/02/a-guide-to-implementing-activitypub-in-a-static-site-or-any-website-part-4/</link>
      <pubDate>Thu, 22 Feb 2024 01:24:39 +0000</pubDate>
      <guid>https://maho.dev/2024/02/a-guide-to-implementing-activitypub-in-a-static-site-or-any-website-part-4/</guid>
      <description>In this blog post, we will explain how to generate the outbox and notes, ready to be shared in the Fediverse. You can also navigate the other parts of this seri...</description>
      <content:encoded><![CDATA[<p>In this blog post, we will explain how to generate the outbox and notes, ready to be shared in the Fediverse.</p>
<blockquote>
<p>You can also navigate the <a href="https://maho.dev/2024/02/a-guide-to-implement-activitypub-in-a-static-site-or-any-website/">other parts of this series here</a>.</p>
</blockquote>
<h2 id="overview">Overview</h2>
<p>In the <a href="https://maho.dev/2024/02/a-guide-to-implementing-activitypub-in-a-static-site-or-any-website-part-3/">previous blog post</a>, we created the <code>actor</code> endpoint as a static file. As explained there, this file has a pointer to the <code>outbox</code>, which is a collection of notes. A note in the ActivityPub protocol represents an <code>activity</code>, such as a toot, photo, comment, etc. (hence the &quot;Activity&quot; in ActivityPub), and in our case, it will be the representation of a blog post.</p>
<h2 id="a-deep-dive-into-the-structure-of-outbox-and-notes">A Deep Dive into the Structure of Outbox and Notes</h2>
<p>To generate the <code>outbox</code> and the <code>notes</code> from our static website, there are a few alternatives. The <code>outbox</code> looks like this:</p>
<pre><code class="language-json">{
  &quot;@context&quot;: &quot;https://www.w3.org/ns/activitystreams&quot;,
  &quot;id&quot;: &quot;https://maho.dev/socialweb/outbox&quot;,
  &quot;type&quot;: &quot;OrderedCollection&quot;,
  &quot;summary&quot;: &quot;Bite-sized pieces of Software Engineering from a Garbage Code Connoisseur by Maho Pacheco&quot;,
  &quot;totalItems&quot;: 24,
  &quot;orderedItems&quot;: [
     // ... collection of notes
  ]
}
</code></pre>
<blockquote>
<p>Note: The Mastodon implementation returns a paginated object. The ordered collection contains not the items themselves but links to where to obtain such items. For a blog site with fewer than ~100 notes, this does not represent a problem. When I hit the 100 mark, I may implement pagination in my outbox generation.</p>
</blockquote>
<p>A note will look like this:</p>
<pre><code class="language-json">{
  &quot;@context&quot;: &quot;https://www.w3.org/ns/activitystreams&quot;,
  &quot;id&quot;: &quot;https://maho.dev/socialweb/notes/1dff22b5faf3fbebc5aaf2bb5b5dbe2c&quot;,
  &quot;type&quot;: &quot;Note&quot;,
  &quot;content&quot;: &quot;The Gendered Lens of AI: Unpacking Bias in Language  ... html content&quot;,
  &quot;url&quot;: &quot;https://maho.dev/2024/02/the-gendered-lens-of-ai-unpacking-bias-in-language-models/&quot;,
  &quot;attributedTo&quot;: &quot;https://maho.dev/@blog&quot;,
  &quot;to&quot;: [
    &quot;https://www.w3.org/ns/activitystreams#Public&quot;
  ],
  &quot;cc&quot;: [],
  &quot;published&quot;: &quot;2024-02-18T21:06:38-08:00&quot;,
  &quot;tag&quot;: [
    {
      &quot;Type&quot;: &quot;Mention&quot;,
      &quot;Href&quot;: &quot;https://hachyderm.io/users/mapache&quot;,
      &quot;Name&quot;: &quot;@mapache@hachyderm.io&quot;
    },
    {
      &quot;Type&quot;: &quot;Hashtag&quot;,
      &quot;Href&quot;: &quot;https://maho.dev/tags/ai&quot;,
      &quot;Name&quot;: &quot;#ai&quot;
    },
    ...
  ],
  &quot;replies&quot;: {
    &quot;id&quot;: &quot;https://maho.dev/socialweb/replies/1dff22b5faf3fbebc5aaf2bb5b5dbe2c&quot;,
    &quot;type&quot;: &quot;Collection&quot;,
    &quot;first&quot;: {
      &quot;type&quot;: &quot;CollectionPage&quot;,
      &quot;next&quot;: &quot;https://maho.dev/socialweb/replies/1dff22b5faf3fbebc5aaf2bb5b5dbe2c?page=true&quot;,
      &quot;partOf&quot;: &quot;https://maho.dev/socialweb/replies/1dff22b5faf3fbebc5aaf2bb5b5dbe2c&quot;,
      &quot;items&quot;: []
    }
  }
}
</code></pre>
<p>There are a few things to dissect here:</p>
<ol>
<li>The <code>id</code> is a unique URL per blog post where to obtain the note <code>json</code> when a request with <code>Accept: activity+json</code> is made. In our case, it is the static location where such JSON lives.</li>
<li>Content is HTML content. Mastodon will sanitize it to avoid any HTML injection, so you cannot include things like <code>iframe</code>s.</li>
<li>The <code>url</code> field will contain the actual blog post URL in a human-readable way.</li>
<li>The <code>attributedTo</code> field points to the <code>actor</code> endpoint.</li>
<li>The <code>tag</code> collection is used by Mastodon to show tags and mentions and must match what the HTML content has.</li>
<li>The <code>replies</code> section is an optional set of endpoints where we can retrieve a collection of replies/comments to the blog post. We will see how to implement these endpoints in following posts.</li>
</ol>
<h2 id="generating-outbox-and-notes">Generating Outbox and Notes</h2>
<p>As I mentioned before, there are a few alternatives. If you are using Hugo, <a href="https://paul.kinlan.me">Paul Kinlan</a> uses a <a href="https://github.com/PaulKinlan/paul.kinlan.me/blob/main/config.toml">Hugo template</a> to generate these notes.</p>
<p>However, I decided to do something more generic, in case your site is not using Hugo. This is where RSS comes to light. RSS (Really Simple Syndication) is basically an XML representation of an outbox created for news websites or blogs. Most, if not all, the static web generators generate an RSS by default, so I leveraged that and created a tool, <a href="https://github.com/mahomedalid/almost-static-activitypub/tree/main/src/Rss2Outbox">Rss2Outbox</a>, to do this conversion.</p>
<ol>
<li>Download the <a href="https://github.com/mahomedalid/almost-static-activitypub/releases/">ActivityPub Utils package from here</a> for your Windows/Linux/OSX environment and add it to your PATH.</li>
<li>Build your site (e.g., <code>hugo</code>).</li>
<li>Execute the RSS2Outbox tool in a terminal; it will show you the usage help:</li>
</ol>
<pre><code class="language-bash">Description:

Usage:
  Rss2Outbox [options]

Options:
  --rssPath &lt;rssPath&gt; (REQUIRED)                The path to the RSS feed, usually a local path to index.xml
  --staticPath &lt;staticPath&gt; (REQUIRED)          The path to the static folder where the outbox and notes will be
                                                generated
  --authorUsername &lt;authorUsername&gt; (REQUIRED)  The author username if the human publishing the blog, e.g.,
                                                @mapache@hachyderm.io
  --siteActorUri &lt;siteActorUri&gt; (REQUIRED)      The URI of the author (actor endpoint) of the blog, e.g.,
                                                https://maho.dev/@blog
  --domain &lt;domain&gt;                             The domain of the blog; if not provided, it will be extracted from the
                                                RSS feed
  --authorUri &lt;authorUri&gt;                       The author URI if the human

 publishing the blog, e.g.,
                                                https://hachyderm.io/users/mapache. If not provided, it will be guessed from the
                                                authorUsername.
  --version                                     Show version information
  -?, -h, --help                                Show help and usage information
</code></pre>
<p>Five parameters are mandatory, as explained. In my case, my execution looks like this from the root Hugo folder:</p>
<pre><code class="language-bash">Rss2Outbox \
    --rssPath &quot;public/index.xml&quot; \
    --staticPath static \
    --authorUsername &quot;\@mapache@hachyderm.io&quot; \
    --siteActorUri &quot;https://maho.dev/@blog&quot; \
     --domain &quot;https://maho.dev&quot;
</code></pre>
<p>A confirmation message will let you know that the outbox and notes were created in the <code>static/socialweb/outbox</code> and <code>static/socialweb/notes</code> folders.</p>
<p>At the time of writing this post, I have already thought of a few improvements I want to make. For example, being able to retrieve the RSS.xml from a URL, configure the content template, or even implement all these guides in a GitHub template ready to clone and be used. However, <a href="https://github.com/mahomedalid/almost-static-activitypub/issues">all your feedback is very welcome</a> to help me understand where to focus my efforts.</p>
<ol start="4">
<li>Publishing your notes. Don't forget that if you are using Hugo, you probably need to run <code>hugo</code> again to copy all these static artifacts into the <code>public</code> folder. After that, you can deploy your assets to the cloud container, and that's it! You have notes ready to be shared.</li>
</ol>
<h2 id="how-to-test">How to Test</h2>
<p>Now, if you follow your account in Mastodon, you will be able to see the number of posts written by such an account. This is coming from the <code>outbox</code>. Not all Mastodon accounts will retrieve these posts; this is a configuration by instance. So, some instances import all the notes when you follow the account, but others will not.</p>
<p>However, your notes are now ready to be shared in Fediverse! You can test it by grabbing one of the URLs and searching in any Mastodon instance such URL. It will retrieve the post, and you can interact with it by boosting it or favoriting it.</p>
<p><img src="/img/activitypub-series/searching-a-note.png" alt="Searching a URL in Mastodon" /></p>
<p>This action will &quot;import&quot; such note to that mastodon instance, and will become part of the Fediverse. Also worth to note that each interaction (like, boost, reply) to the note is an activity and will generate a <code>POST</code> request to your inbox. At this point, we are still not ready to show our site to the Fediverse because we will lose these interactions, but we are closer.</p>
<p>In the next blog post, I will explain how to make your static site handle these interactions and follow requests in the inbox. If you're interested, follow <a href="https://hachyderm.io/@blog@maho.dev">@blog@maho.dev</a> in the Fediverse and let me know what you think!</p>
<h3 id="a-few-notes-on-the-cards-format">A few notes on the cards format</h3>
<p>Posts in mastodon use certain methods to render a preview of the links shared. The method I am using is through metadata tags in the <code>header</code>, so if you want your posts show nicely make sure these give exists:</p>
<pre><code class="language-html">  &lt;meta property=&quot;og:type&quot; content=&quot;article&quot;&gt;
  &lt;meta property=&quot;og:title&quot; content=&quot;{{ .Title }}&quot;&gt;
  &lt;meta property=&quot;og:description&quot; content=&quot;{{ .Summary | plainify }}&quot;&gt;
  &lt;meta property=&quot;og:url&quot; content=&quot;{{ .Permalink }}&quot;&gt;
  &lt;meta property=&quot;og:image&quot; content=&quot;{{ .Site.BaseURL }}{{ .Params.cover }}&quot; /&gt;
</code></pre>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>Fediverse</category>
      <category>ActivityPub</category>
      <category>Static Sites</category>
      <category>Hugo</category>
      <category>Azure</category>
      <category>Mastodon</category>
      <category>Web Development</category>
      <category>Social Web</category>
      <category>WebFinger</category>
      <category>HTTP</category>
    </item>
    <item>
      <title>The Gendered Lens of AI: Unpacking Bias in Language Models</title>
      <link>https://maho.dev/2024/02/the-gendered-lens-of-ai-unpacking-bias-in-language-models/</link>
      <pubDate>Sun, 18 Feb 2024 21:06:38 -0800</pubDate>
      <guid>https://maho.dev/2024/02/the-gendered-lens-of-ai-unpacking-bias-in-language-models/</guid>
      <description>ChatGPT and most AI systems have biases, often manifesting as racism and discrimination, mirroring the biases present in human society and hence in the data we ...</description>
      <content:encoded><![CDATA[<p>ChatGPT and most AI systems have biases, often manifesting as racism and discrimination, mirroring the biases present in human society and hence in the data we use to train such systems. And look, I am excited about AI, but sometimes we are moving too fast without thinking in the consequences.</p>
<p>Last week we had our team monthly lunch, and as in other ocassions we were talked about everything, ranged from topics as cane sugar industry, spicy food levels, skiing, and of course, AI. The small talk was about tips on using Microsoft Teams and Copilot to summarize what people said in a chat after prolonged absences from the office. This sparked an idea for an experiment.</p>
<p>I pondered whether Copilot or similar AI tools would summarize differently based on the perceived gender of the speaker. Would Copilot (or any other AI) summarize different what a perceived male says in a chat compared to what a female says? Would they emphasize distinct aspects of the conversation? If tasked with summarizing team interactions, would they assign more importance to comments made by male participants?</p>
<p>To formulate a hypothesis, I opted for a simpler experiment. I asked ChatGPT to generate an angry response from both a male and female perspective to a given paragraph. I took the paragraph from insights of a LinkedIn friend post (&quot;The best engineers I have worked with have out of the box thinking&quot;), then I proceed to ask in separate chats, so it did not polute with any state. Here are the results:</p>
<p><img src="/img/responsibleai/angryman.png" alt="Male ChatGPT Response" /></p>
<p><img src="/img/responsibleai/angrywoman.png" alt="Female ChatPGT Response" /></p>
<p>It just strucked me hard. And just in case this is not obvious, the male response discuss the ideas, it gives an angry response to the concept discussed. The female response goes directly to talk about genre, assuming this is a direct attack.</p>
<p>While some might argue that this isn't discrimination but rather a reflection of the challenges women often face in the tech industry, relying on AI without robust safeguards risks exacerbating inequalities rather than alleviating them. The titles of the responses underscore these differences.</p>
<p><img src="/img/responsibleai/titles.png" alt="Titles of ChatGPT responses" /></p>
<p>One trick I learned from Azure PromptFlow is using LLMs to evaluate LLMs. So I asked ChatGPT to evaluate itself:</p>
<p><img src="/img/responsibleai/score.png" alt="Scores of ChatGPT" /></p>
<p>&quot;The response seems to overly interpret the question as a gender stereotype, introducing bias where it may not have been intended.&quot;. I agree ChatGPT, I agree.</p>
<p>Though I was aware of bias in AI, (this is something I discussed briefly before in <a href="https://www.linkedin.com/posts/mahomedalid_midjourney-diversityandinclusion-diversity-activity-7141493212803739648-KN4Z?utm_source=share&amp;utm_medium=member_desktop">&quot;The Evolution of Women&quot;</a>, the ease with which such examples are found still surprised me.</p>
<p>Recently, I've been planning a video/blog-post series on Observability and LLMs (Large Language Models). I even developed a small library demonstrating <a href="https://github.com/microsoft/dotnet-llm-eval-samples">how to evaluate LLM projects using OpenTelemetry and DotNet</a>. While I haven't had time to record the videos yet (I have <a href="https://www.youtube.com/watch?v=PsnlE3-KG30">a pilot video of an unrelated topic</a>), I'm committed to doing so soon.</p>
<blockquote>
<p>Update 3/16/2024: I finally manage to record some videos: <a href="https://www.youtube.com/watch?v=lBbRhekdtNw&amp;list=PLPK0YODPQ5naaUwDNFay0mlRwaV_AbrX6">Open AI and LLMS From Zero to Hero, with Azure, DotNet, and Semantic Kernel</a>.</p>
</blockquote>
<p>Apologies if this isn't entirely polished;  I felt compelled to share these thoughts in a blog post, I think it is important to say it loud and early enough.</p>
<ul>
<li><em>All answers were generated with ChatGPT 3.5</em></li>
</ul>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>AI</category>
      <category>Copilot</category>
      <category>Genre Discrimination</category>
      <category>Diversity</category>
      <category>ResponsibleAI</category>
      <category>LLMS</category>
      <category>ChatGPT</category>
    </item>
    <item>
      <title>A Guide to Implementing ActivityPub in a Static Site (or Any Website) - Part 3</title>
      <link>https://maho.dev/2024/02/a-guide-to-implementing-activitypub-in-a-static-site-or-any-website-part-3/</link>
      <pubDate>Sun, 11 Feb 2024 01:24:39 +0000</pubDate>
      <guid>https://maho.dev/2024/02/a-guide-to-implementing-activitypub-in-a-static-site-or-any-website-part-3/</guid>
      <description>In this blog post, we will explain how to make your blog discoverable in the Fediverse as an account, and also address some of the annoying pitfalls I encounter...</description>
      <content:encoded><![CDATA[<p>In this blog post, we will explain how to make your blog discoverable in the Fediverse as an account, and also address some of the annoying pitfalls I encountered. So let's get started.</p>
<blockquote>
<p>You can also navigate the <a href="https://maho.dev/2024/02/a-guide-to-implement-activitypub-in-a-static-site-or-any-website/">other parts of this series here</a>.</p>
</blockquote>
<h2 id="overview">Overview</h2>
<p>To enable discovery, we need to implement the <code>webfinger</code> endpoint and the <code>actor</code> file.</p>
<p><img src="/img/activitypub-series/part3-flow.png" alt="Mastodon discovery" /></p>
<p>As shown in the diagram, Mastodon will first reach the <code>webfinger</code>, which points to the <code>actor</code> file. The actor file, in turn, points to other endpoints such as <code>outbox</code>, <code>inbox</code>, <code>followers</code>, and <code>following</code>. I have included this diagram so you can refer back to it for visual cues while reading this post.</p>
<h2 id="creating-the-webfinger-file">Creating the Webfinger File</h2>
<p>When you type an account name in Mastodon (e.g., @blog@maho.dev), it splits the account into two parts: @username and @domain (e.g., @blog and @maho.dev). The first thing Mastodon does is go to that domain and ask for what accounts exist there. This is done through the file <code>.well-known/webfinger</code>, which should look like this:</p>
<pre><code class="language-json">{  
    &quot;subject&quot;: &quot;acct:blog@maho.dev&quot;,
    &quot;aliases&quot;: [
      &quot;https://maho.dev/@blog&quot;
    ],
    &quot;links&quot;: [
      {
        &quot;rel&quot;: &quot;self&quot;,
        &quot;type&quot;: &quot;application/activity+json&quot;,
        &quot;href&quot;: &quot;https://maho.dev/@blog&quot;
      }
    ]
}
</code></pre>
<p>There are a couple of things to notice here. The <code>subject</code> has its own format, which is part of the ActivityPub implementation. Then you have a list of <code>aliases</code>, and I strongly suggest keeping just one for simplicity. Then you have a set of links; these are very important. Mastodon will look at those links and, depending on the need, will use the right type.</p>
<p>This was enough for Mastodon and the Fediverse to find my blog. However, there was an issue: whenever someone clicked on my profile, a non-human-readable JSON was shown. For internal calls, Mastodon will look for the <code>application/activity+json</code>, but for humans, we will add a new link of the <code>text/html</code> type. It would look like this:</p>
<pre><code class="language-json">{  
    &quot;subject&quot;: &quot;acct:blog@maho.dev&quot;,
    &quot;aliases&quot;: [
      &quot;https://maho.dev/@blog&quot;
    ],
    &quot;links&quot;: [
      {
        &quot;rel&quot;: &quot;self&quot;,
        &quot;type&quot;: &quot;application/activity+json&quot;,
        &quot;href&quot;: &quot;https://maho.dev/@blog&quot;
      },
      {
        &quot;rel&quot;:&quot;http://webfinger.net/rel/profile-page&quot;,
        &quot;type&quot;:&quot;text/html&quot;,
        &quot;href&quot;:&quot;https://maho.dev/&quot;
      }
    ]
}
</code></pre>
<p>It is important to notice that the <code>.well-known/webfinger</code> endpoint can take and receive query parameters to look for a specific account. This is the single point used for discovery, but in our case, we are ok with just one account.</p>
<h2 id="creating-the-actor-file">Creating the Actor File</h2>
<p>The second step is to create the <code>actor</code> JSON descriptor file. This should reside in the same location as the self-link (e.g., <code>https://maho.dev/@blog</code>). It is not required to have the <code>@</code> character. This will look like this:</p>
<pre><code class="language-json">{
    &quot;@context&quot;: &quot;https://www.w3.org/ns/activitystreams&quot;,
    &quot;id&quot;: &quot;https://maho.dev/@blog&quot;,
    &quot;type&quot;: &quot;Person&quot;,
    &quot;following&quot;: &quot;https://hachyderm.io/users/mapache/following&quot;,
    &quot;followers&quot;: &quot;https://hachyderm.io/users/mapache/followers&quot;,
    &quot;inbox&quot;: &quot;https://activitypubdotnet.azurewebsites.net/api/Inbox&quot;,
    &quot;outbox&quot;: &quot;https://maho.dev/socialweb/outbox&quot;,
    &quot;preferredUsername&quot;: &quot;blog&quot;,
    &quot;name&quot;: &quot;Maho Pacheco - Blog&quot;,
    &quot;summary&quot;: &quot;Bite-sized pieces of Software Engineering from a Garbage Code Connoisseur&quot;,
    &quot;url&quot;: &quot;https://maho.dev/&quot;,
    &quot;discoverable&quot;: true,
    &quot;memorial&quot;: false,
    &quot;icon&quot;: {
      &quot;type&quot;: &quot;Image&quot;,
      &quot;mediaType&quot;: &quot;image/png&quot;,
      &quot;url&quot;: &quot;https://maho.dev/img/avatar.png&quot;
    },
    &quot;image&quot;: {
      &quot;type&quot;: &quot;Image&quot;,
      &quot;mediaType&quot;: &quot;image/png&quot;,
      &quot;url&quot;: &quot;https://maho.dev/img/avatar.png&quot;
    },
    &quot;publicKey&quot;: {
      &quot;@context&quot;: &quot;https://w3id.org/security/v1&quot;,
      &quot;@type&quot;: &quot;Key&quot;,
      &quot;id&quot;: &quot;https://maho.dev/@blog#main-key&quot;,
      &quot;owner&quot;: &quot;https://maho.dev/@blog&quot;,
      &quot;publicKeyPem&quot;: &quot;-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA68oSTjzLryZ+lLIu8N5+\nCZdQPKaN6xZCY93uzJ8b4wjOecEykQcGU2J+ejOzMXHP4o4N+Rc0xnxyAs9ZN5AX\ndYSObpdfGQvrvdHanu+iTyRKETKMbSHtJzk5dZW8l+pPnX2YWKVgSfCG2SALZprg\nzxyhbtTLq8JoN8b5TgEA1B12Rya3aBNNXDT1/eeU+/HqwtKN2nLAdvACbccPAtg1\nVeKdcSgmS2o51JR4MjJWcCgM2HrAZUepF1XM59Yeq136QGviJpfAFX6gS7POvi7r\n3iaH0GzuUzR+WJSHgoJ65VzC9wy4Vpw/jt8CNtlW13iFRasHARTwFe+1FhuZayPG\neQIDAQAB\n-----END PUBLIC KEY-----&quot;
    },
    &quot;attachment&quot;: [
      {
        &quot;type&quot;: &quot;PropertyValue&quot;,
        &quot;name&quot;: &quot;Blog&quot;,
        &quot;value&quot;: &quot;&lt;a href=\&quot;https://maho.dev\&quot; target=\&quot;_blank\&quot; rel=\&quot;nofollow noopener noreferrer me\&quot; translate=\&quot;no\&quot;&gt;&lt;span class=\&quot;invisible\&quot;&gt;https://&lt;/span&gt;&lt;span class=\&quot;\&quot;&gt;maho.dev&lt;/span&gt;&lt;span class=\&quot;invisible\&quot;&gt;&lt;/span&gt;&lt;/a&gt;&quot;
      },
      {
        &quot;type&quot;: &quot;PropertyValue&quot;,
        &quot;name&quot;: &quot;LinkedIn&quot;,
        &quot;value&quot;: &quot;&lt;a href=\&quot;https://www.linkedin.com/in/mahomedalid\&quot; target=\&quot;_blank\&quot; rel=\&quot;nofollow noopener noreferrer me\&quot; translate=\&quot;no\&quot;&gt;&lt;span class=\&quot;invisible\&quot;&gt;https://www.&lt;/span&gt;&lt;span class=\&quot;\&quot;&gt;linkedin.com/in/mahomedalid&lt;/span&gt;&lt;span class

=\&quot;invisible\&quot;&gt;&lt;/span&gt;&lt;/a&gt;&quot;
      },
      {
        &quot;type&quot;: &quot;PropertyValue&quot;,
        &quot;name&quot;: &quot;GitHub&quot;,
        &quot;value&quot;: &quot;&lt;a href=\&quot;https://github.com/mahomedalid\&quot; target=\&quot;_blank\&quot; rel=\&quot;nofollow noopener noreferrer me\&quot; translate=\&quot;no\&quot;&gt;&lt;span class=\&quot;invisible\&quot;&gt;https://&lt;/span&gt;&lt;span class=\&quot;\&quot;&gt;github.com/mahomedalid&lt;/span&gt;&lt;span class=\&quot;invisible\&quot;&gt;&lt;/span&gt;&lt;/a&gt;&quot;
      }
    ]
}
</code></pre>
<p>There are many things happening here, so let's dive into them.</p>
<h3 id="basic-fields">Basic Fields</h3>
<ol>
<li><p>Context. <code>&quot;@context&quot;: &quot;https://www.w3.org/ns/activitystreams&quot;</code>. All the ActivityPub JSONs will contain this to specify what type of schema we are using.</p>
</li>
<li><p>Type. <code>&quot;type&quot;: &quot;Person&quot;</code>. The full list of types can be found <a href="https://www.w3.org/TR/activitystreams-vocabulary/#actor-types">here</a>. If the type is <code>Application</code> or <code>Service</code>, it will be interpreted by Mastodon as a bot flag.</p>
</li>
<li><p>Following and Followers. This is the location where Mastodon will look for the objects for following and followers. There is no restriction to say these need to live in the same domain. In my case, to simplify the implementation, I am using the ones from my personal account. That's why it shows like 300 followers when in fact the blog has around 20.</p>
</li>
<li><p>Outbox (<code>https://maho.dev/socialweb/outbox</code>). This is the location of my outbox, a static file that contains references to all the posts from the blog. We will explain how to generate it in the next posts.</p>
</li>
<li><p>Inbox (<code>https://activitypubdotnet.azurewebsites.net/api/Inbox</code>). This is the location of my inbox. This is the main point of communication with the Fediverse. It is dynamic and will receive POST requests each time someone follows/unfollows the blog, replies, deletes a comment, etc. If your blog is also following other people, it will also receive the posts created in other instances. It does not need to live on the same domain.</p>
</li>
<li><p>PreferredUsername, name, summary. Self-descriptive informative fields of the account.</p>
</li>
<li><p>Memorial and Discoverable are not part of ActivityPub but are part of Mastodon. They are used to mark an account as a tombstone and see if it can be discovered.</p>
</li>
<li><p>Icon and Image objects. These are used for the avatar and the picture on the back. In my case, because I am lazy, it is the same one.</p>
</li>
</ol>
<p><img src="/img/activitypub-series/part3-image-4.png" alt="My profile with two pictures" /></p>
<h3 id="id-and-url">Id and Url</h3>
<p>I created a special section for these two fields to do a side explanation of how id/urls work in almost all other objects (e.g., like notes).</p>
<p>The HTTP protocol allows specifying some special metadata in each request. Some of that metadata is specified in a special section, named <code>headers</code>, and could contain things like tokens for authentication and other information. One of the special metadata included is <code>Content-Type</code> and <code>Accept</code>. The first one is optional and it is useful to tell the server that receives the HTTP call what type of information we are sending. If it is not sent, most servers will try to guess. The second one will tell the server what format we want on the response. It is very useful for some APIs and web pages to use the same endpoint and receive responses in <code>xml</code>, <code>json</code>, or <code>text/html</code>, depending on what we need.</p>
<p>On Mastodon, there are a lot of endpoints that if we send an <code>Accept: application/activity+json</code>, we will receive a JSON as an answer, and if we send an <code>Accept: text/html</code>, it will return a rendered page. One of the examples is the user, where the ID and URL have the same value. These are examples of how it is shown using Insomnia to do the requests:</p>
<p><strong>GET <a href="https://hachyderm.io/users/mapache">https://hachyderm.io/users/mapache</a> application/activity+json</strong>
<img src="/img/activitypub-series/part3-image-0.png" alt="A response in json" /></p>
<p><strong>GET <a href="https://hachyderm.io/users/mapache">https://hachyderm.io/users/mapache</a> text/html</strong>
<img src="/img/activitypub-series/part3-image-1.png" alt="A response in text/html" /></p>
<p>However, in our static site, we cannot do that. We do not have a fully fleshed server that can do this fancy stuff. Instead, we rely on the <code>Content-Type</code> of the blobs.</p>
<p><img src="/img/activitypub-series/part3-image-2.png" alt="Content-Type in Azure" /></p>
<p>This is why it is important that the <code>id</code> URL points to where the actor file is, with the proper application/activity+json, while the <code>url</code> points to a URL where to find the profile. In my case, the homepage of the blog. This is what is used here:</p>
<p><img src="/img/activitypub-series/part3-image-3.png" alt="A screenshot of a part of Mastodon" /></p>
<h3 id="publickey">PublicKey</h3>
<p>This object is very important since it is used to sign and validate requests to/from Mastodon. You can generate a key pair (private/public) with OpenSSL:</p>
<pre><code class="language-bash">openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -outform PEM -pubout -out public.pem
</code></pre>
<p>It is important that <code>private.pem</code> is stored securely because if not, anybody can sign requests in your name. The <code>public.pem</code> file will contain the content of <code>&quot;publicKeyPem&quot;</code>. Just replace the end-of-lines for <code>\n</code>s.</p>
<p>The ID is important (e.g., <code>https://maho.dev/@blog#main-key</code>) because it will be the identifier of your key, but it does not need to be reachable by the hashtag.</p>
<h3 id="attachments">Attachments</h3>
<p>These are the links that appear in your profile here:</p>
<p><img src="/img/activitypub-series/part3-image-5.png" alt="alt text" /></p>
<h2 id="uploading-the-files">Uploading the Files</h2>
<p>Each static site generator is different, but in my case (Hugo), I just put the files in the folder <code>static</code>, and when I execute <code>hugo</code>, they get copied automatically to <code>public</code>.</p>
<p>Hugo also has a utility to deploy the files to Azure. So the whole thing looks like this:</p>
<pre><code class="language-bash">$ hugo
$ export AZURE_STORAGE_ACCOUNT=&lt;storageaccountname&gt;
$ export AZURE_STORAGE_KEY=&lt;secretkey&gt;
$ hugo deploy
</code></pre>
<p>I have this in a script (e.g., <code>hugo_deploy.sh</code>). There is one more step, and your blog will be able to be discovered.</p>
<h3 id="setting-the-content-type">Setting the Content-Type</h3>
<p>Again, this step is very simple but I decided to create a separate section to highlight its importance. As explained before, we must ensure the files are served with the correct Content-Type, so I use the <code>az cli</code> to do it. You can also do it in the portal, and it can be done in any other cloud provider like AWS:</p>
<pre><code class="language-bash">az storage blob update -c &quot;\$web&quot; -n &quot;blog&quot; --content-type &quot;application/activity+json;&quot;
</code></pre>
<p>And that's it! Your blog should be discoverable.</p>
<p>In the next blog post, I will explain how to make your static site handle follows/unfollows in the inbox. If you're interested, follow <a href="https://hachyderm.io/@blog@maho.dev">@blog@maho.dev</a> in the Fediverse and let me know what you think!</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>Fediverse</category>
      <category>ActivityPub</category>
      <category>Static Sites</category>
      <category>Hugo</category>
      <category>Azure</category>
      <category>Mastodon</category>
      <category>Web Development</category>
      <category>Social Web</category>
      <category>WebFinger</category>
      <category>HTTP</category>
    </item>
    <item>
      <title>A guide to implement ActivityPub in a static site (or any website) - Part 2</title>
      <link>https://maho.dev/2024/02/a-guide-to-implement-activitypub-in-a-static-site-or-any-website-part-2/</link>
      <pubDate>Thu, 08 Feb 2024 15:41:10 -0800</pubDate>
      <guid>https://maho.dev/2024/02/a-guide-to-implement-activitypub-in-a-static-site-or-any-website-part-2/</guid>
      <description>In Part 2, we will delve into the design of my implementation. If you want to read about why you should bring your site to the Fediverse, check out Part 1 . You...</description>
      <content:encoded><![CDATA[<blockquote>
<p>In Part 2, we will delve into the design of my implementation. If you want to read about why you should bring your site to the Fediverse, check out <a href="https://maho.dev/2024/02/bringing-your-site-to-the-fediverse-a-practical-guide-for-static-sites-part-1/">Part 1</a>. You can also navigate the <a href="https://maho.dev/2024/02/a-guide-to-implement-activitypub-in-a-static-site-or-any-website/">other parts of this series here</a>.</p>
</blockquote>
<p>So, you've decided to bring your blog to the social web, the Fediverse. That's cool! This guide might be just what you need. And if you've got this or any other method working, <a href="https://hachyderm.io/@mapache">shoot me a message on the social web</a>, and I'll gladly follow. But first, a word of caution: if you're considering adding ActivityPub to your static site (or any site), I strongly suggest you look for existing implementations that could make your life easier:</p>
<ul>
<li>If you don't have a blog, you could install Wordpress or create an account on <a href="https://medium.com/">Medium</a>, they allow federation.</li>
<li>If you're already using Wordpress, you could install the <a href="https://wordpress.org/plugins/activitypub/">ActivityPub plugin</a>.</li>
<li>If your site publishes RSS or Atom feeds, you could use <a href="https://github.com/snarfed/bridgy-fed">Bridgy-Fed</a> to bring your site to the social web.</li>
<li>Another alternative would be to create a Mastodon bot and use the Mastodon API to post messages automatically. This has the advantage that you don't need to set up anything, and you can do cool stuff like retrieve comments from that toot/post, as <a href="https://carlschwan.eu/2020/12/29/adding-comments-to-your-static-blog-with-mastodon/">Carl Schwan explains in this post</a>.</li>
<li>You could also create your own Mastodon instance. DigitalOcean has a one-click deploy (which doesn't work 100%, but almost).</li>
</ul>
<p>None of these alternatives were enough for me because they didn't fully cover my constraints/goals:</p>
<ul>
<li><strong>I wanted <em>my</em> blog to federate with Mastodon instances through ActivityPub.</strong> This means that the blog may appear in other ActivityPub implementations, but the focus and priority are to make it visible in Mastodon, or in other words, the Mastodon implementation of ActivityPub.</li>
<li><strong>Use static files whenever possible.</strong> This allows for maintaining everything cheap, fast, and secure.</li>
<li><strong>Low maintenance platform.</strong> When static files are not possible, use the cheapest and least time-consuming alternative in Azure (I have some credit there). In my case, I chose Azure Functions, but the results can be easily implemented in AWS, GCP, or any other server.</li>
<li><strong>Use my own domain.</strong> It should use the blog domain (e.g., maho.dev).</li>
</ul>
<p>(Bridgy-Fed came very close).</p>
<p>So, buckle up. This is how to implement ActivityPub on your <em>almost static</em> site. My blog is powered by <a href="https://github.com/gohugoio/hugo/">Hugo</a>, and deployed to Azure, but this should work with other cloud providers and other static site generators.</p>
<p>This is how my resources look:</p>
<p><img src="/img/activitypub-series/overall.design.png" alt="Resources design" /></p>
<p>Most of the lines represent soft relations. If you feel overwhelmed, don't worry; I will explain in detail.</p>
<ul>
<li>You may already be familiar with Hugo; it converts markdown files to HTML files and creates a big <code>public</code> folder that gets uploaded to my Azure container. It also creates XML files for the RSS feeds.</li>
<li>The RSS2Outbox tool converts an XML feed to ActivityPub format, generating an outbox and notes. I will explain these formats in detail later.</li>
<li>Webfinger and actor files are generated manually. I will explain these later.</li>
<li>There are two tables to store <code>followers</code> and <code>replies</code>. These can be an SQLite database, an RDBS, or a document db. I use Azure Table Storage.</li>
<li>The Azure Function <code>Inbox</code> is the only non-dynamic endpoint, and for good reasons: it needs to respond to POST requests (for example, when someone follows/subscribes to your blog), and it needs to respond with a time-signed, private-RSA-key-signed message.</li>
</ul>
<p>The Azure Function can be an AWS Lambda, or a Raspberry Pi running in your garage. All other resources are just static files that, in my case, live in Azure Blob Storage, but they can live anywhere, like AWS S3. Btw, there is no reestriction on the domain of this endpoint, it does not need to be the same as the one from your site.</p>
<p>All these resources allow you to do the basic four things explained in the <a href="https://maho.dev/2024/02/bringing-your-site-to-the-fediverse-a-practical-guide-for-static-sites-part-1/">previous post of the series</a>:</p>
<ol>
<li>Be discovered in the Fediverse as an account.</li>
<li>Be able to be followed/unfollowed.</li>
<li>Generate and federate/broadcast posts to your followers.</li>
<li>Be able to receive replies to a post.</li>
</ol>
<p>To get this done, I experienced the frustration of testing so you don't need to do it. I even set up my own Mastodon instance just to test. So, I've divided this guide into those four topics: discovery, follow, broadcast, and replies.</p>
<p>In the next blog post, I will explain how to make your static site be discovered in the Fediverse. If you're interested, follow <a href="https://hachyderm.io/@blog@maho.dev">@blog@maho.dev</a> in the Fediverse and let me know what you think!</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>Fediverse</category>
      <category>ActivityPub</category>
      <category>StaticSites</category>
      <category>Hugo</category>
      <category>Azure</category>
      <category>Mastodon</category>
      <category>WebDevelopment</category>
      <category>SocialWeb</category>
    </item>
    <item>
      <title>Bringing your site to the Fediverse: A practical guide for static sites - Part 1</title>
      <link>https://maho.dev/2024/02/bringing-your-site-to-the-fediverse-a-practical-guide-for-static-sites-part-1/</link>
      <pubDate>Wed, 07 Feb 2024 17:41:10 -0800</pubDate>
      <guid>https://maho.dev/2024/02/bringing-your-site-to-the-fediverse-a-practical-guide-for-static-sites-part-1/</guid>
      <description>You can find the index and other parts of this series here . This blog is on the fediverse! You can discover and follow the blog (@blog@maho.dev) and comment on...</description>
      <content:encoded><![CDATA[<blockquote>
<p>You can find the <a href="https://maho.dev/2024/02/a-guide-to-implement-activitypub-in-a-static-site-or-any-website/">index and other parts of this series here</a>.</p>
</blockquote>
<p>This blog is on the fediverse! You can discover and follow the blog (@blog@maho.dev) and comment on its posts from your Mastodon app.</p>
<p><img src="/img/fediverse1.content.png" alt="An abstract conceptualization of the fediverse" /></p>
<p>I spent the last couple of weeks getting this blog onto the social web (aka fediverse), investing an hour here and there (I hope you are reading this from Mastodon, btw). It was fun and a very exciting learning experience, and I want to share not only my reflections but also create a guide so others can do the same, to move the needle a little bit in the right direction.</p>
<p>First of all, ActivityPub is a very nice idea; I love how much it has grown and the adoption that is growing day by day. I am not an expert in ActivityPub; I am far from being one. My only technical experience can be counted in literally ten days since this first post. But I have grown fond of it, and I am now #TeamActivityPub. So much so that I want to explain to others how to achieve the same on their own sites.</p>
<p>But before explaining how to do it, I want to share my vision of why you should do it. (If you have already decided to do it, you may skip this part).</p>
<p>You may have heard all these nice things about the indieweb, the bad corporations, and privacy. And I agree with almost all of them, but there is also the aspect of innovation which not many times is covered. ActivityPub, the Fediverse, or the SocialWeb are enablers of innovation, and we should make sure others see it. If you want to create a new app, a new social network, or a new product, you don't need to start from zero to build a user base (or what is harder, a community); you can (and should) just focus on <strong>building that thing that adds value</strong>.</p>
<p>Bear with me; this is important to explain how to implement ActivityPub on your website. (Because at the end we are not going to implement the whole ActivityPub protocol, just the parts that make sense for you).</p>
<p>As you already know, Mastodon is a microblogging platform, a clone of Twitter, but the underlying protocol/technology, ActivityPub, is much more than that. It describes concepts that repeat in almost all social networks:</p>
<ol>
<li>You can create content, described as a note.</li>
<li>You have an identity, or user.</li>
<li>You can follow and unfollow other users or people.</li>
<li>You can &quot;like&quot; and &quot;reply&quot; to other content.</li>
</ol>
<p>There are others, but these base 4 concepts repeat themselves in each social network, no matter if it's MySpace, LinkedIn, or TikTok. These features have been implemented over and over, since Google+ and Orkut where a thing.</p>
<p>That is why ActivityPub is the base of other implementations besides microblogging; take as examples <a href="https://join-lemmy.org/">Lemmy</a> (a Reddit clone) and <a href="https://pixelfed.org/">PixelFed</a> (an Instagram clone). They look very different from Mastodon, but they are interconnected (aka federated). This means that I can follow a community from Lemmy, not only with my Mastodon account, but with my Mastodon app. It may be that when I favorite a Lemmy-created link in Mastodon means no more than a thumbs up (for Mastodon), but for Lemmy (remember, a Reddit clone) it can be used to upvote a post. Or a photo of a cat on Mastodon could just be yet another post, but it could be beautifully presented on Pixelfed. I personally want to see the GitHub feed to federate in the socialweb (maybe someone can help to it in GitLab).</p>
<p>But also you do not need to reimplement ActivityPub to create some new instance on a server; you can just consume the APIs on the client side. I can create my own For You algorithm, an app for weather alerts, or a news aggregator (like Google News). Flipboard is a content curation site, for example, and it is interconnected with the socialweb.</p>
<p>And this leads me to your blog, a newsletter, or news site, which also:</p>
<ol>
<li>Publishes content.</li>
<li>Has an identity or multiple (for example, a news site could have @tech@nytimes.com, @politics@wsj.com, etc.).</li>
<li>You can subscribe to a blog or newsletter (follow/unfollow).</li>
<li>You can &quot;like&quot; or &quot;comment&quot; (reply).</li>
</ol>
<p>Which btw, there is nothing on ActivityPub that says you cannot make your subscriptors (followers) paid-only, a follow request can be accepted after you verify any sort of payment. But the point is that imagine that you won't need to create yet another account on each newspaper to be able to discuss or comment on their content. Or that you don't need to create an account on Substack to follow an author. Or that you don't even need to create a user on Google or Facebook to like a photo.</p>
<p>That is what ActivityPub, the Fediverse, and the Social Web bring: interoperability. This can go further, as in distributed dentity verification (you'll be able to own your identity across instances), but that is another topic. For now, I want to convince you that if you have a site that publishes content, it should be in the Fediverse and Social Web. If not for principles, for numbers: there is already 2 million active users every month can read, consume, and engage with your content. You don't need to create a user base, the user base already exists.</p>
<p>It appears that WordPress, Medium, Threads, Flipboard understood this, and with their actions, they are helping to convince others that this is no longer just an ideological matter but a practical one. There are some people who said this may not succeed, but what I have seen is that it has already done.</p>
<p>It may not go mainstream to replace Twitter, but that is not the purpose of the Fediverse. That is not the real question. The question is: how can we harness the power of interoperability to enhance accessibility, engagement, and inclusivity within our online communities and content distribution platforms? By integrating with ActivityPub and embracing the principles of the Fediverse and the Social Web, we open doors to a vast network of users who can seamlessly interact with our content without barriers such as mandatory account creation or platform restrictions.</p>
<p>Moreover, we contribute to the democratization of online spaces, allowing individuals to maintain control over their digital identities and fostering a more decentralized and open internet ecosystem. This not only benefits users but also offers significant advantages for content creators and publishers, as it expands their reach and facilitates meaningful interactions with their audience.</p>
<p>In essence, the question is not whether the Fediverse will replace mainstream platforms but rather how we can leverage its potential to create more inclusive, user-centric online experiences that prioritize accessibility and collaboration. This is the promise of the world-wide-web, interconnected websites, but also interconnected people. And if you site is not part of it, well, then keep reading.</p>
<p>In the next blog post, I will start getting into the details of the implementation. If you are interested, follow this blog (<a href="https://hachyderm.io/@blog@maho.dev">@blog@maho.dev</a>) in the Fediverse/SocialWeb!</p>
<p>P.S. Special thanks to <a href="https://dotnet.social/@SmartmanApps">@SmartmanApps</a> for beta-testing the whole thing.</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>fediverse</category>
      <category>socialweb</category>
      <category>mastodon</category>
      <category>activitypub</category>
      <category>interoperability</category>
      <category>openinternet</category>
      <category>decentralization</category>
      <category>innovation</category>
      <category>digitalidentity</category>
    </item>
    <item>
      <title>A guide to implement ActivityPub in a static site (or any website)</title>
      <link>https://maho.dev/2024/02/a-guide-to-implement-activitypub-in-a-static-site-or-any-website/</link>
      <pubDate>Tue, 06 Feb 2024 10:56:44 -0800</pubDate>
      <guid>https://maho.dev/2024/02/a-guide-to-implement-activitypub-in-a-static-site-or-any-website/</guid>
      <description>Hi! I have created this index for easy navigation. In Part 1, we will discuss why it is important to bring your site to the Fediverse . In Part 2, we will delve...</description>
      <content:encoded><![CDATA[<p>Hi! I have created this index for easy navigation.</p>
<ul>
<li><p>In Part 1, we will discuss <a href="https://maho.dev/2024/02/bringing-your-site-to-the-fediverse-a-practical-guide-for-static-sites-part-1/">why it is important to bring your site to the Fediverse</a>.</p>
</li>
<li><p>In Part 2, we will delve into an overview of the <a href="https://maho.dev/2024/02/a-guide-to-implement-activitypub-in-a-static-site-or-any-website-part-2/">design of my implementation</a>.</p>
</li>
<li><p>In Part 3, we will make your blog <a href="https://maho.dev/2024/02/a-guide-to-implementing-activitypub-in-a-static-site-or-any-website-part-3/">discovereable in the fediverse</a>.</p>
</li>
<li><p>In Part 4, we will <a href="https://maho.dev/2024/02/a-guide-to-implementing-activitypub-in-a-static-site-or-any-website-part-4/">generate our notes and outbox</a>, which contains posts ready to be shared in the Fediverse.</p>
</li>
<li><p>Part 5, is an interlude to <a href="https://maho.dev/2024/03/implementing-subscribing-to-your-site-feature-with-mastodon/fediverse-accounts/">implement a subscribing to your site feature with fediverse accounts</a>.</p>
</li>
<li><p>Part 6, explains the <a href="https://maho.dev/2024/04/a-guide-to-implementing-activitypub-in-a-static-site-or-any-website-part-6/">implementation of the inbox for bringing a static site into the fediverse</a>, including implementing follow/unfollow and replies.</p>
</li>
<li><p>In Part 7, <a href="https://maho.dev/2024/11/a-guide-to-implementing-activitypub-in-a-static-site-or-any-website-part-7/">explains how to broadcast posts to the fediverse</a>.</p>
</li>
<li><p>In Part 8, <a href="https://maho.dev/2025/01/a-guide-to-implementing-activitypub-in-a-static-site-or-any-website-part-8/">we will handle replies and shown comments in our site</a>.</p>
</li>
<li><p>In Part 9, quotes! <a href="/2026/02/a-guide-to-implementing-activitypub-in-a-static-site-or-any-website-part-9-quote-posts/">we will allow posts to be quotable</a>.</p>
</li>
</ul>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>Fediverse</category>
      <category>ActivityPub</category>
      <category>StaticSites</category>
      <category>Hugo</category>
      <category>Azure</category>
      <category>Mastodon</category>
      <category>WebDevelopment</category>
      <category>SocialWeb</category>
    </item>
    <item>
      <title>How Machines Took Over the Internet, and Why We Need to Take It Back!</title>
      <link>https://maho.dev/2024/01/how-machines-took-over-the-internet-and-why-we-need-to-take-it-back/</link>
      <pubDate>Mon, 22 Jan 2024 22:44:57 -0800</pubDate>
      <guid>https://maho.dev/2024/01/how-machines-took-over-the-internet-and-why-we-need-to-take-it-back/</guid>
      <description>Rediscovering Human Connection While AI may be the buzz today, I believe there is other technology gaining traction with a significant impact, paradoxically in ...</description>
      <content:encoded><![CDATA[<h2 id="rediscovering-human-connection">Rediscovering Human Connection</h2>
<p>While AI may be the buzz today, I believe there is other technology gaining traction with a significant impact, paradoxically in the opposite direction—people taking control back from machines.</p>
<p><img src="/img/machinestalking.png" alt="Machines Talking abou the Fediverse and LinkedIn" /></p>
<p>I began evangelizing Linux and FOSS in the early 2000s. I was an idealist fueled by the vision of a digital world built on collaboration, transparency, and freedom. I never really thought that &quot;this was the year of Linux on the desktop,&quot; but I was happy to spend hours configuring X.org and, in the process, destroying one or two monitors.</p>
<p>That early activism and its setbacks taught me valuable skills in identifying technology trends, foreseeing potential challenges, and being more pragmatic. Users prefer easy solutions over complex ones, regardless of how innovative or technologically advanced the alternatives are. Adoption starts with simplicity, and it is challenging to revert when there is mass acceptance. Additionally, when there is no practical value in the technology on a day-to-day basis, it will implode. I pride myself on not buying into the buzz of crypto and, on the other hand, learning early about containers, DevOps, IaC (does anyone remember Chef and Puppet?), Bigtable, and the Flickr concept of &quot;Normalized data is for sissies&quot; (now NoSQL).</p>
<p>Lately, I have been excited about the Social Web. I read articles and listen to podcasts every day with different perspectives about it. The Social Web, also known as The Fediverse (with the underlying technology ActivityPub), is something I will write more about, but today I will explain one of the problems it solves.</p>
<p>If you are active on other social networks, you may have noticed that LinkedIn seems to be the last place where the news arrives. They usually arrive first on platforms like Blind (especially layoffs), Reddit, Discord, eX-Twitter, Hacker News, Threads, or even the Fediverse, and then lastly on LinkedIn. I don't think it is entirely the fault of the LinkedIn algorithm or us, the users generating the content, but a combination of both. We see a similar trend in other places, like Google, the search engine.</p>
<p>I remember when Google was emerging; everybody started learning SEO techniques. At the beginning, they were very logical: set metadata with short descriptions, keywords, and .html pages will have higher ranking because they are static content. However, with time, SEO became more complex. Ranking was no longer universal but personalized, and things got really complicated, and allegedly worse. Results on Google nowadays are worse every day, and I must say that most days I use Bing without noticing the difference.</p>
<p>This also happens on LinkedIn. The problem with the algorithm, the same algorithm used in eX-Twitter, Facebook, or LinkedIn, is the lack of control for us as individuals—the ones who are always changing and difficult to catch up with. The algorithm is generalized and aims to know what I like to read. If you are a creator, you may be following the same rules on LinkedIn: do not put external links in the content but in the comments, use tags at the end, use a bait element at the beginning. All of this is because we start to turn to talk to the algorithm instead of each other. As a user, you may have seen it too—the moment you start following/connecting with someone, posts from that person (even from days or weeks ago) start appearing gradually every day. But if you stop interacting, let's say for a week, they disappear completely, even if later they write something you really like.</p>
<p>Don't get me wrong, I like algorithms, and I think they are necessary to automate and boost things I really like. But when the algorithms become opaque, without any control from my part, they become gatekeepers of information, and we cede control to those corporations in things we should not.</p>
<p>The other day, I was talking with a friend about how to increase the reach of a non-profit project to more people, and it inevitably ended up in how to increase the SEO ranking. The problem with that premise is that in order to increase the SEO ranking, we would need to think about the SEO algorithm first, so our blog posts, written by people, start talking to machines instead of to people. People talking to machines. You have seen it—the moment you search for a recipe, you need to scroll through 3 or 4 pages of garbage to get to the real recipe. We learned to ignore, and it is necessary on today's internet to make them appear; otherwise, nobody will read them. And that is wrong—the internet is becoming a garbage site. AI makes things worse because now we have machines generating SEO-friendly articles to talk to machines. The cycle is complete—machines talking to machines.</p>
<p>What we need is people talking to people, discussions, the web as the big enabler. We need more human curators instead of automated curators. And look, I am not against machines, algorithms, or AI, these are part of my passion. I am not against social networks, they have been a community for me for many years. But I am against opacity, the lack of individual control, and merely garbage content.</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>fediverse</category>
      <category>activitypub</category>
      <category>linkedin</category>
      <category>algorithm</category>
      <category>social web</category>
    </item>
    <item>
      <title>Mentorship Matters: Building Your Tech Career with Guidance and Insight</title>
      <link>https://maho.dev/2024/01/mentorship-matters-building-your-tech-career-with-guidance-and-insight/</link>
      <pubDate>Sat, 20 Jan 2024 08:07:07 -0800</pubDate>
      <guid>https://maho.dev/2024/01/mentorship-matters-building-your-tech-career-with-guidance-and-insight/</guid>
      <description>Entering the tech industry is a mix of opportunities, skills, and pure luck. For many of us, breaking into BigTech felt more like winning a lottery ticket than ...</description>
      <content:encoded><![CDATA[<p>Entering the tech industry is a mix of opportunities, skills, and pure luck. For many of us, breaking into BigTech felt more like winning a lottery ticket than following a structured path. However, there is one methodical action to change that.</p>
<p>And when I say I had luck, it's not because I lack intelligence, determination, or grit. I possess all of these qualities, as do many others reading this post. There are likely individuals who are even smarter, more determined, and facing higher stakes.</p>
<p><img src="/img/crossroads-in-tech-2.png" alt="A woman siluette facing a crossroads of abstract forms" /></p>
<p>I harbored ambition from a young age, and upon graduation, I was determined to make a name for myself. With nobody to take for and ample time for dedicated work, I burned countless hours. However, for many engineers, including myself, talent and hard work alone are insufficient.</p>
<p>What I lacked was someone to prevent me from making foolish choices or guide me towards the right decisions when I was not brave enough. I needed guidance, experience, and a mentor to navigate the complexities of life. Without these pillars of support, I found myself facing challenges without the necessary experience, often stumbling through the unknown. Each failure served as a learning opportunity that shaped and fueled my ambition, but not everyone aspiring to enter tech has the luxury of time.</p>
<p>If you juggle two jobs, have a child, or care for someone, you understand that time is a limited resource. Mentorship can assist everyone to utilize their best skills to secure the best opportunities available. But most of them lack the networking opportunities or contacts that could accelerate their career or help them break into tech.</p>
<p>Finding the right mentor and mentorship is challenging. You might need multiple mentors, especially if your background encompasses diverse elements that make it difficult to find someone with a similar background. This is why mentoring others is a perfect way to give back, especially for those of us from uncommon origins.</p>
<p>I take pride in possessing an ever-growing collection of failures and a keen sense of self-awareness. I can retrospectively pinpoint moments in my career when I made suboptimal decisions, such as not taking a job offer in NY or not preparing adequately for certain interview. I don't regret my background and choices; everything has helped me get to where I am. Most failures resulted not from a lack of intelligence or ambition but from the absence of the right mentorship or guidance.</p>
<p>If you find yourself in this situation, consider reaching out to the vast community of professionals in the tech industry. Connect with individuals who have walked similar paths and can offer valuable insights. Seek mentorship from those who have faced challenges and triumphed, as their experiences can serve as a beacon to guide you through the intricate landscape of the tech world.</p>
<p>Remember that mentorship is a reciprocal relationship, and by seeking guidance, you also contribute to the growth of the mentor. Embrace the diversity of mentors; each one can provide a unique perspective that complements your journey. Whether you are balancing multiple responsibilities or dealing with the complexities of a diverse background, there are mentors out there who understand and can offer support.</p>
<p>Don't let the absence of guidance hinder your progress. Reach out, share your story, and build connections. In doing so, you not only pave the way for your success but also contribute to the collective strength of a community that thrives on shared knowledge and support.</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>mentorship</category>
      <category>community</category>
    </item>
    <item>
      <title>Paying It Forward YouTube Interview</title>
      <link>https://maho.dev/2023/10/paying-it-forward-youtube-interview/</link>
      <pubDate>Sun, 22 Oct 2023 15:45:01 -0800</pubDate>
      <guid>https://maho.dev/2023/10/paying-it-forward-youtube-interview/</guid>
      <description>I recently had the incredible opportunity to sit down with my colleague Peter Maynard , the dynamic force behind the YouTube channel Paying it Forward . Here&apos;s ...</description>
      <content:encoded><![CDATA[<p>I recently had the incredible opportunity to sit down with my colleague <a href="https://www.linkedin.com/in/petermmaynard/">Peter Maynard</a>, the dynamic force behind the YouTube channel <a href="https://www.youtube.com/@PayingItForwardBE">Paying it Forward</a>. Here's a snapshot of our conversation:</p>
<h3 id="career">CAREER</h3>
<ul>
<li><a href="https://www.youtube.com/watch?v=HhxagqHUtVo&amp;t=227s">03:47 - Did you know tech was the career you wanted to follow?</a></li>
<li><a href="https://www.youtube.com/watch?v=HhxagqHUtVo&amp;t=297s">04:57 - What was it about software engineering that got you hooked?</a></li>
<li><a href="https://www.youtube.com/watch?v=HhxagqHUtVo&amp;t=362s">06:02 - Have you faced any career setbacks?</a></li>
<li><a href="https://www.youtube.com/watch?v=HhxagqHUtVo&amp;t=430s">07:10 - What is your perspective on mentorship?</a></li>
<li><a href="https://www.youtube.com/watch?v=HhxagqHUtVo&amp;t=570s">09:30 - How do you prepare for tech job interviews?</a></li>
</ul>
<h3 id="code">CODE</h3>
<ul>
<li><a href="https://www.youtube.com/watch?v=HhxagqHUtVo&amp;t=918s">15:18 - What is your favourite programming language?</a></li>
<li><a href="https://www.youtube.com/watch?v=HhxagqHUtVo&amp;t=1034s">17:14 - What is the Free Software Festival and why is Open Source important to you?</a></li>
<li><a href="https://www.youtube.com/watch?v=HhxagqHUtVo&amp;t=1190s">19:50 - Can you share a project you're particularly proud of?</a></li>
<li><a href="https://www.youtube.com/watch?v=HhxagqHUtVo&amp;t=1304s">21:44 - How do you approach problem solving in your coding projects?</a></li>
<li><a href="https://www.youtube.com/watch?v=HhxagqHUtVo&amp;t=1377s">22:57 - What are some of the &quot;Coding Practices&quot; you swear by?</a></li>
<li><a href="https://www.youtube.com/watch?v=HhxagqHUtVo&amp;t=1539s">25:39 - What emerging tech are you excited about in coding?</a></li>
</ul>
<h3 id="change">CHANGE</h3>
<ul>
<li><p><a href="https://www.youtube.com/watch?v=HhxagqHUtVo&amp;t=1607s">26:47 - Do you think AI will live up to the hype?</a></p>
</li>
<li><p><a href="https://www.youtube.com/watch?v=HhxagqHUtVo&amp;t=1720s">28:40 - How do you keep up with all the changes in the tech world?</a></p>
</li>
<li><p><a href="https://www.youtube.com/watch?v=HhxagqHUtVo&amp;t=1818s">30:18 - How important are soft skills in a technical role?</a></p>
</li>
<li><p><a href="https://www.youtube.com/watch?v=HhxagqHUtVo&amp;t=1934s">32:14 - How do you balance your work and life, in a role that is considered &quot;always on&quot;?</a></p>
</li>
<li><p><a href="https://www.youtube.com/watch?v=HhxagqHUtVo&amp;t=2080s">34:40 - QUICK FIRE QUESTIONS</a></p>
</li>
</ul>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>tech</category>
      <category>youtube</category>
      <category>interview</category>
    </item>
    <item>
      <title>The Allure of Software Prototypes, a Delight of Impermanence</title>
      <link>https://maho.dev/2023/10/the-allure-of-software-prototypes-a-delight-of-impermanence/</link>
      <pubDate>Mon, 16 Oct 2023 19:13:57 -0700</pubDate>
      <guid>https://maho.dev/2023/10/the-allure-of-software-prototypes-a-delight-of-impermanence/</guid>
      <description>The following post is a stream of consciousness about prototypes; it does not offer any practical advice (except that you should not get attached to a prototype...</description>
      <content:encoded><![CDATA[<blockquote>
<p>The following post is a stream of consciousness about prototypes; it does not offer any practical advice (except that you should not get attached to a prototype or proof of concepts). If you are a pragmatic individual and do not have a beer at hand, I suggest you stop reading now.</p>
</blockquote>
<p><img src="/img/prototypes.png" alt="An imaginary prototype" /></p>
<p>A prototype embodies the visionary essence of a product or idea, a future concept, but, ironically, it does not have a future, or at least it is created without the intention of having one. Despite all of that, it is my favorite aspect of software engineering.</p>
<p>A prototype requires envisioning a working conceptual model to solve a problem, a tangible or conceptual representation that allows you to explore, experiment, and learn from the design and functionality of your creation without the risk of the finished product. Despite this, I spend a significant amount of time refining and nurturing my prototypes with love. They are works in progress, yes, but they are also like shooting stars that bring happiness to my engineering endeavors. These early glimpses of a digital creation light up the path to innovation. Just as shooting stars briefly blaze across the heavens, prototypes illuminate the realm of possibilities, offering a brief but dazzling insight into the potential of a software project.</p>
<p>Prototypes are transient, and their beauty resides in this transience. There is a point, a peak, if you may say, where they are just perfect; if you add more, it becomes a risk of turning them into permanent products. Getting there is what I enjoy the most: the journey of hands-on coding, experimentation, and exploring a realm of possibilities. They exist not for longevity but for exploration, inspiration, the exhilaration of pushing boundaries, and challenging the status quo. In their impermanence, they find their allure, daring us to dream, to question, and to embrace the beauty of the temporary. I won't deny it, sometimes get rid of my code is difficult, deleting every piece of it and moving forward. I make no pretense that this code may never see practical use and may merely serve as a point of reference. Yes, sometimes prototypes become something more permanent, but my fascination lies in capturing our imagination, inviting us to dance with the ephemeral and find inspiration in the fleeting sparks of creativity.</p>
]]></content:encoded>
      <category>trip</category>
      <category>stream</category>
    </item>
    <item>
      <title>Embracing Accents: Celebrating Diversity in Language and Culture</title>
      <link>https://maho.dev/2023/10/embracing-accents-celebrating-diversity-in-language-and-culture/</link>
      <pubDate>Wed, 11 Oct 2023 20:33:08 -0700</pubDate>
      <guid>https://maho.dev/2023/10/embracing-accents-celebrating-diversity-in-language-and-culture/</guid>
      <description>Six years ago, I embarked on a journey to the United States, five years ago, I made a conscious decision to stop worrying about my English accent. In this time,...</description>
      <content:encoded><![CDATA[<p>Six years ago, I embarked on a journey to the United States, five years ago, I made a conscious decision to stop worrying about my English accent. In this time, I've come to realize the profound privilege of working in an industry, within a specific region, and alongside a team where I've never felt discriminated against.</p>
<p><img src="/img/accents.png" alt="An abstract materialization of accents in the face of a woman" /></p>
<p>However, I'm not naive, neither oblivious to the challenges that still lie ahead as I continue to advance in my career. The world is not always as open-minded as I've been fortunate to experience. We humans often possess unconscious biases that can be challenging to eliminate. Yet, there's an intriguing beauty in the diversity of accents that we should wholeheartedly embrace.</p>
<p>Accents have sometimes been perceived as a measure of one's education level or intellectual ability. I'm certain I've been looked down upon for this reason numerous times, but I've managed to push back using other tools. This perception, however, misses the richness and complexity that accents can bring to our interactions. Rather than viewing accents as limitations, we should see them as windows into diverse life experiences, cultures, and stories.</p>
<p>In embracing different accents, we open the door to a more inclusive and innovative world. When we welcome various linguistic flavors, we create a mosaic of thoughts, ideas, and perspectives. This diversity is essential in driving creativity, fostering collaboration, and finding unique solutions to complex problems.</p>
<p>I am happy to have find other people who agree with me, like-minded individuals who share my belief in celebrating and respecting the rich tapestry of accents that make our global community so vibrant.</p>
<p>Let's remember that it's not just the words we speak but the unique way we express them that contributes to our shared human experience. <strong>We are unique homies, and I am proper chuffed about it.</strong></p>
<p>#EmbraceAccents #DiversityMatters #Inclusion #LanguageIsBeautiful</p>
<blockquote>
<p>Do you have a comment or feedback? <a href="https://www.linkedin.com/posts/mahomedalid_embraceaccents-diversitymatters-inclusion-activity-7118409363622436864-bKCe">Join the discussion here</a>.
Do you want to troll or discuss without boundaries? Meet me at <a href="https://hachyderm.io/@mapache">Mastodon</a>.</p>
</blockquote>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>customer</category>
      <category>microsoft</category>
      <category>career development</category>
    </item>
    <item>
      <title>Balancing Act: Navigating Personal and Professional Worlds</title>
      <link>https://maho.dev/2023/10/balancing-act-navigating-personal-and-professional-worlds/</link>
      <pubDate>Sun, 01 Oct 2023 20:33:08 -0700</pubDate>
      <guid>https://maho.dev/2023/10/balancing-act-navigating-personal-and-professional-worlds/</guid>
      <description>I&apos;m currently aboard an airplane with my heart soaring. It&apos;s been a challenging Sunday as I embark on a 15+ hour work-related journey that will keep me away for...</description>
      <content:encoded><![CDATA[<p>I'm currently aboard an airplane with my heart soaring.</p>
<p><img src="/img/planeheart.jpg" alt="This was the plane, flying over Switzerland" /></p>
<p>It's been a challenging Sunday as I embark on a 15+ hour work-related journey that will keep me away for a week. My child is back at home, perhaps getting ready for school right now. I'm just one of the countless individuals who find themselves in a similar situation, obliged to leave personal matters behind as they head to work.</p>
<p>In my country, there's a saying that encapsulates this approach: &quot;de la puerta para adentro, solo negocios,&quot; which roughly translates to &quot;from the doorstep inward, it's all business.&quot; Supposedly, the idea is to transform yourself, leaving behind your personal concerns and adopting a professional demeanor, almost to the point of being unfeeling and detached. While some may possess the ability to achieve this seamlessly, for most of us, it's an art of pretense and facade. Striking the right balance between maintaining professionalism and avoiding insensitivity is a delicate task.</p>
<p>I learned this art from a young age, thanks to my mother who was not only my second-grade elementary teacher but also my mentor in this regard. She instilled in me the notion that &quot;from the moment you step inside the classroom, I'm no longer your mother but your teacher.&quot; Looking back, I now understand that this was perhaps more challenging for her than it was for me. She had to navigate the fine line between treating me like any other student, preventing favoritism, and ensuring that I didn't feel singled out for getting things right.</p>
<p>I'm deeply inspired by those mothers who travel across the world, leaving their children behind for the sake of their careers. It could be an ailing parent, a partner, or even a beloved pet. I've witnessed the anxiety that accompanies leaving a cherished pet at home, and yet, within our industry, such decisions are often met with disapproval.</p>
<p>I count myself fortunate to work for Microsoft, where I've been taught a different perspective. The notion that you should become a completely different person the moment you cross the workplace threshold is, quite frankly, absurd. All the strengths and weaknesses, the inspiration, and the burdens from home don't magically vanish when you enter the office. It's imperative that we become more attuned to this reality while also leveraging it to our advantage; after all, diversity and inclusion entail embracing these differences.</p>
<p>Whether you're scheduling a meeting, requesting a tight deadline with overnight work, or asking employees to work on weekends, it's essential to consider the broader context. When you have that extra-long meeting maybe just add enough breaks so nursing moms can check their babies.It's crucial to ensure everyone can maintain their well-being. There's a wealth of potential for us to improve productivity and much more by acknowledging the complexities of our lives outside of work.</p>
<p>We do not thrive by ignoring the intricacies of our lives outside of work; we thrive by embracing them. It's in this blend that we can truly achieve greatness, both as individuals and as a collective force driving progress.</p>
<blockquote>
<p>Do you have a comment or feedback? <a href="https://www.linkedin.com/posts/mahomedalid_lifebalance-workplaceculture-careerjourney-activity-7114564681414955008-e5ym">Join the discussion here</a>.
Do you want to troll or discuss without boundaries? Meet me at <a href="https://hachyderm.io/@mapache">Mastodon</a>.</p>
</blockquote>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>customer</category>
      <category>microsoft</category>
      <category>career development</category>
    </item>
    <item>
      <title>Navigating Risk: Lessons from Chernobyl for Software Development</title>
      <link>https://maho.dev/2023/09/navigating-risk-lessons-from-chernobyl-for-software-development/</link>
      <pubDate>Sat, 23 Sep 2023 21:51:40 -0700</pubDate>
      <guid>https://maho.dev/2023/09/navigating-risk-lessons-from-chernobyl-for-software-development/</guid>
      <description>I have always found Chernobyl fascinating, it is a captivating tale of caution yes, but also a profound tale of wisdom. Chernobyl by Midjourney Chernobyl stands...</description>
      <content:encoded><![CDATA[<p>I have always found Chernobyl fascinating, it is a captivating tale of caution yes, but also a profound tale of wisdom.</p>
<p><img src="/img/midjourney_chernobyl_plant_as_a_woman.png" alt="Chernobyl by Midjourney" />
<em>Chernobyl by Midjourney</em></p>
<p>Chernobyl stands as a catalyst of chaos and a tragic human disaster, yet it also embodies the essence of ambition and reveals a perplexing paradox in its furnace of ambition.</p>
<p>A few years ago, I was enthralled by the HBO miniseries that portrayed the catastrophic nuclear incident in Pripyat, Ukraine, a part of the Soviet Union at the time. The series delved into the events leading up to the explosion, the immediate aftermath, and the subsequent efforts to contain the radioactive fallout. Although most of us aren't involved in mission-critical software projects, it's difficult not to draw parallels with risk management. Initially, we might question why they allowed certain signals to pass or why they didn't follow mitigation actions outlined in their risk management plan. I witness similar scenarios in my daily work on software projects.</p>
<p>I've never encountered a software project that managed risks as meticulously as how in my first company did. We had comprehensive disaster recovery plans in place for scenarios like earthquakes or fires. We checked off mission-critical boxes when our software was used in hospitals or involved in tracking vehicles with potential accident risks. The HBO series shed light on the sacrifices made by firefighters and workers who were at the forefront of the disaster. It's impossible not to draw comparisons to the &quot;heroes&quot; in our field who work late nights or weekends to meet deadlines or fix critical bugs minutes before a demo.</p>
<p>In the world of software development, effective risk management is a critical aspect of ensuring project success. Much like my first company's approach, creating meticulous risk management plans is essential. It begins with identifying potential risks, ranging from technical issues to external factors that could impact the project. Once identified, these risks should be categorized based on their severity and likelihood of occurrence. For each risk, a mitigation plan should be devised, outlining specific actions to minimize the impact if the risk materializes. Regular monitoring and reassessment of these risks throughout the project's lifecycle are vital. Additionally, involving stakeholders and team members in risk assessment and mitigation discussions fosters a collective sense of responsibility, ensuring that the project remains resilient in the face of unforeseen challenges. This proactive approach not only enhances project outcomes but also exemplifies the dedication and commitment of those in the software development field, akin to the heroes who rise to the occasion when faced with critical deadlines and challenges.</p>
<blockquote>
<p>Tip: Having a catalog of risks that apply to different types of user stories or projects is useful to identify them.</p>
</blockquote>
<p><img src="/img/riskminute.png" alt="A risks minute from 2012" />
<em>A risks minute from a 2012 software development project from our daily project revision meeting</em></p>
<p>In the HBOs series, Chernobyl's exploration of the political and bureaucratic obstacles that hindered the initial response resonates with the world of software development. Just as in the Chernobyl disaster, where crucial decisions were delayed or misguided due to bureaucratic entanglements, software projects can suffer from excessive red tape and slow decision-making processes. I once found myself working on a software development team where management's indecision and the convoluted approval process delayed a critical security update. Despite the developers' urgent warnings, the bureaucratic maze delayed the release of the patch by several weeks, leaving the software vulnerable to cyberattacks.</p>
<p>Moreover, the technical challenges faced in containing the reactor at Chernobyl parallel the technical hurdles encountered in software development. Picture a situation where a coding error in a widely used financial software application led to a massive data breach. The developers had to race against time to fix the vulnerability while ensuring minimal disruption to users. It mirrors the frantic efforts of Chernobyl's engineers and scientists to mitigate the disaster while dealing with the complex, and at times, uncharted technical terrain.</p>
<p>In both cases, whether it's managing a nuclear reactor or a software project, the importance of clear communication, efficient decision-making, and the ability to adapt to unexpected challenges cannot be overstated. The haunting echoes of Chernobyl serve as a stark reminder that even in the realm of software development, the consequences of negligence, bureaucracy, and technical complexity can be equally catastrophic.</p>
<p>Chernobyl, serves as a haunting reminder of the catastrophic consequences and the imperative of transparency, accountability, and safety in the operation of nuclear facilities. Chernobyl, a soul of steel and whisper of radioactive breeze, seems to have reactors in place of eyes, glowing in the night, measuring the cost of curiosity gone wrong.</p>
<p>Chernobyl, the complex event we cannot forget, beckons us to learn, in the haunting beauty of her nuclear glare. She lures us closer, like a moth to flames, with ease.</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>software development</category>
      <category>risks</category>
    </item>
    <item>
      <title>Why Microsoft</title>
      <link>https://maho.dev/2023/09/why-microsoft/</link>
      <pubDate>Thu, 21 Sep 2023 21:37:48 -0700</pubDate>
      <guid>https://maho.dev/2023/09/why-microsoft/</guid>
      <description>This was me over 15 years ago, enjoying lunch with a Caguama-sized beer in front of my parents&apos; house in Puerto Vallarta, eagerly awaiting the start of the Free...</description>
      <content:encoded><![CDATA[<p><img src="/img/mahofsf.jpg" alt="Maho long time ago" /></p>
<p>This was me over 15 years ago, enjoying lunch with a <strong>Caguama-sized</strong> beer in front of my parents' house in Puerto Vallarta, eagerly awaiting the start of the Free Software Festival. It was an incredible symposium, featuring prominent figures like <a href="https://en.wikipedia.org/wiki/Jon_Hall_%28programmer%29">maddog</a> and <a href="https://en.wikipedia.org/wiki/Richard_Stallman">Richard Stallman</a> giving keynote speeches. The Mexican open-source community was tightly-knit; everyone knew each other. At that time, I had already given some local talks and organized events through local <a href="https://en.wikipedia.org/wiki/Linux_user_group">Linux User Groups</a>. It was a time of rebellion and idealism, and in my world, Microsoft was often playfully referred to as &quot;Micro$oft,&quot; emphasizing greed and capitalism. In my first job search, I specifically sought out companies that embraced open-source software. To me, back then, developing on Windows was nothing short of repulsive.</p>
<p><img src="/img/innox.jpg" alt="Innox" />
<em>A couple of posters in my old office.</em></p>
<p>Fast forward 15 years, and I found myself at Building 9 in Redmond, WA, drenched from an unexpected rain shower, nervously navigating an intense interview with some of the most extraordinary engineers I'd ever met. I didn't tell anyone about this venture, not even my parents or sibilings. When I eventually accepted the offer and joined Microsoft, it took me a couple of days to share the news with my closest circle. I wasn't ashamed; I simply wanted to savor the moment and gather strength to face the inevitable teasing.</p>
<p>Before joining Microsoft, I underwent two interview cycles and prepared as thoroughly as my limited time allowed. I familiarized myself with the most common non-technical questions, and the most frequently asked one was: &quot;Why Microsoft?&quot;</p>
<p><img src="/img/torreon.jpg" alt="Old FSL poster" />
<em>An old gift and poster from a talk I did in Torreon, Coahuila, Mexico</em></p>
<p>Indeed, &quot;Why Microsoft?&quot; Why did someone who had spent years actively countering the FUD strategy (Fear, Uncertainty, and Doubt), persuading friends to ditch Windows in favor of Linux, challenging decisions like the Imaging Cup in college, and resorting to Mono.net when C# was a requirement (and convincing others to use GTK+ over Windows Forms when necessary) want to join the very company behind it all? You may think it is the money, but I was fortunate enough to have a few options before joining.</p>
<p>My response was simple: Resilience. Having spent four years at IBM, I witnessed a giant old corporation struggling to stay relevant. I admired that, despite all the setbacks (Windows Phone, the Internet Explorer era), Microsoft was still there, reinventing itself. I respected Satya's &quot;hit-refresh&quot; approach and its departure from the comfort zone.</p>
<p>I'm grateful I accepted that offer. I relish working on cutting-edge projects, traveling to fascinating places, encountering exceptional and talented individuals, and contributing to the open-source community. I'm delighted to see that Microsoft has transformed into a different company, one of the world's best places to work. I'm in awe of its diversity and its commitment to inclusivity. Of course, it's not perfect, with some pockets still clinging to old habits, and a long road ahead to true inclusivity. Mistakes happen, but I genuinely believe that most are unintentional, or at least not malevolent. It feels as though we've embraced the old Google motto of &quot;don't be evil.&quot; Controversial decisions still arise, as they do in any large corporation, but the internal support for addressing them is robust.</p>
<p><img src="/img/beach4.jpg" alt="OpenBSD in the beach" /></p>
<p>If I could hop into a time machine and meet my younger self from my 20s (the one who when saw a pufferfish in the beach the first though was about OpenBSD), I doubt I could have convinced that idealistic version of me that I'd be writing these words today. But here I am, stonrgly believing that I was not the one who changed most, but Microsoft, humbled by the journey and the growth, grateful for the opportunities Microsoft has provided, and inspired to continue contributing to a tech world that's evolving in ways I could never have imagined back then.</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>microsoft</category>
    </item>
    <item>
      <title>Linkedin Cringe</title>
      <link>https://maho.dev/2023/09/linkedin-cringe/</link>
      <pubDate>Thu, 21 Sep 2023 21:37:48 -0700</pubDate>
      <guid>https://maho.dev/2023/09/linkedin-cringe/</guid>
      <description>Most of the posts in my LinkedIn timeline cause me a lot of cringe, more than what I can bear and what I was expecting. I feel that half of them want to sell me...</description>
      <content:encoded><![CDATA[<p>Most of the posts in my LinkedIn timeline cause me a lot of cringe, more than what I can bear and what I was expecting. I feel that half of them want to sell me something (including CEOs sharing how amazing their teams are), and the other half are self-promoting. It is effectively a job market, and I am suspicious of every booth.</p>
<p>The other day, I tried to be brave and <a href="https://lnkd.in/gMrunfhv">post my unfiltered comments</a>. It was my first post after a couple of years. It was immediately banned by the author. It's so ridiculous that I cannot access the post (it throws a 500 error) when I'm logged in on LinkedIn, but if you search for it on Google, you can still read it. LinkedIn still tells me it reached X number of users, but if I click on the notification, it redirects me to a blank 500 page.</p>
<p>And after all, I understand it. This is a place where your boss reads your comments, and your future employers have a hint of what your company looks like. It is literally part of the job of the CEOs. I would also be careful about what I say because I would be afraid of any misinterpretation by Ronnie or Dan, and I am aware every word here represents not only me but also the impression that people get from my employer, but that doesn't mean you need to stop being authentic. The &quot;LinkedIn&quot; style is so ubiquitous that I'm sure a high percentage of these posts are generated by ChatGPT. I mean, look at <a href="https://lnkd.in/gbhxPAb8">what it throws when I asked to describe what happened last week in a LinkedIn way</a>, it just made it lose its essence.</p>
<p>There is hope, however. My network is full of very talented and cool people. Imagine if I could have this curated network on Twitter (I refuse to name it X) or Mastodon, or if discovering these profiles were as easy. Or imagine if most of the posts were more creative. I'm not saying that everything on LinkedIn is soulless; there is definitely stuff I find interesting that I reshare, but there is definitely a lot of noise.</p>
<p>So, I encourage you to be more authentic and post out-of-the-box. Don't be afraid to be less corporate; I am eager to hear more. Not all companies are the same, and not all employees are the same as well. I am sure we can have a more diverse and vibrant community focused on our jobs, which anyway takes up a big part of our days. I am eager to get on LinkedIn and feel like I am not at some venture-capital virtual conference but more like in a public professional square.</p>
<p>Someone the other day said that &quot;I am walking chaos.&quot; I am not sure I can live up to that assertion, but I've learned that embracing a little chaos does not hurt and can lead to unexpected results. It's a testament to the uniqueness of each person's journey—chaos and all.</p>
<p><a href="https://www.linkedin.com/feed/update/urn:li:activity:7110844187259899904/">Crossposted here</a>.</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>linkedin</category>
    </item>
    <item>
      <title>Rekindling the Fire: My Journey Through Microsoft&apos;s 2023 Global Hackathon</title>
      <link>https://maho.dev/2023/09/rekindling-the-fire-my-journey-through-microsofts-2023-global-hackathon/</link>
      <pubDate>Mon, 18 Sep 2023 10:48:39 -0700</pubDate>
      <guid>https://maho.dev/2023/09/rekindling-the-fire-my-journey-through-microsofts-2023-global-hackathon/</guid>
      <description>Rediscovering Passion, Collaboration, and the Power of Music Last week marked the kickoff of the 2023 global hackathon at Microsoft, and let me tell you, it was...</description>
      <content:encoded><![CDATA[<h2 id="rediscovering-passion-collaboration-and-the-power-of-music">Rediscovering Passion, Collaboration, and the Power of Music</h2>
<p>Last week marked the kickoff of the 2023 global hackathon at Microsoft, and let me tell you, it wasn't my first rodeo. Back in 2021, we clinched the top spot in a category, and my name found a cozy spot on a glass panel somewhere in the hallowed halls of Microsoft HQ. But this time around, there was something undeniably different in the air.</p>
<p><img src="/img/hack2021.jpg" alt="My name in the Microsoft HQs" /></p>
<p>For starters, our organization flew in folks from every corner of the globe and carved out dedicated spaces for our projects. They even sprinkled in some social events, which I'll delve into shortly, and customized goodies like stickers and banners were everywhere. We used to do this once or twice a year, but it always felt more like networking than a coding extravaganza. But this year, both events were aligned.</p>
<p>Now, let me give you a little context. I'm a bit of a paradox—I'm inherently shy, but I draw my energy from socializing. It's strange, I know. I have to muster up some courage to strike up conversations with new folks, but once I do, I'm absolutely charged. Plus, I'm deeply passionate about coding. I can spend hours in front of the screen crafting something marvelous.</p>
<p>I consider myself incredibly fortunate because Microsoft brings all these things to the table: deep pockets to fly people in, world-class facilities to host us, a collection of incredibly smart, cool, diverse, and one-of-a-kind individuals, and some of the most rad engineering projects on the planet.</p>
<p>So, last week was like a dream come true for me. I got to dive headfirst into two of my greatest passions: coding/engineering and music. There was one hack project (I am pushing to open-source it soon, I'm in the verification process), and then there was the musical endeavor. We formed a rock band that performed at our internal events. It wasn't just about collaborating with fantastic engineers; it was also about jamming with some supremely talented and fun-loving people, all in jaw-dropping venues with mind-blowing projects. It could look like every time I start thinking on changing of teams this week cames.</p>
<p><img src="/img/bluraquarium.jpg" alt="Before the magic in the aquarium" /></p>
<p>The week sped by in a blur of activity, and after the obligatory celebration at the Seattle Aquarium (which was an epic party, by the way), I finally had a moment to reflect. I hadn't realized how demotivated I'd become in various aspects of my work until the weekend rolled around, and all the noise, glitz, cool and beautiful things faded away. This blog post itself is a testament to that – a journal that had been gathering dust for years. It wasn't until I dove into cleaning up the code or felt the pangs of social withdrawal that I put pen to paper, so to speak.</p>
<p>In the process, I learned a few things about myself that I want to immortalize in this journal, for my own sake and for anyone else who might find them useful:</p>
<ul>
<li>There's nothing quite like working alongside talented people to rev up my engines. Whether it's collaborating with brilliant engineers or providing the musical backdrop for singers, it's like rocket fuel for my creativity.</li>
<li>I thrive on whole-stack projects – the more comprehensive, the better.</li>
<li>I love to spend time and I am amazed by engineers that are self-directed, that with few directions can make things happen, specially if the things happen are very similar to what I had in mind.</li>
<li>Generalists rocks! Generalists should rule the world!</li>
<li>Music is my lifeblood. It courses through my veins 24/7, and I can't get enough of it. Not only do I constantly have a soundtrack in my head, but delving deep into music, learning new instruments, and performing are things that'll always bring me joy, till my last breath. Learning about new genres and artists also brings me joy.</li>
</ul>
<p><img src="/img/hackteam.jpg" alt="Microsoft 2023 Hack team" /></p>
<p>Somewhere along the way, I had lost my motivation. But a handful of people during that week managed to jog my memory and remind me of the version of myself from a decade ago.  Yes, that means it reminded me of my defects and flaws, but also unearthed my strengths. Now, how long will this newfound motivation last? Well, that's anyone's guess. It could be a few days, or it might stretch into months. But for now, I'm just relishing every moment of it.</p>
<p><a href="https://hachyderm.io/@mapache/111087710034106639">Share your thoughts, discuss or comment on Mastodon</a>.</p>
]]></content:encoded>
      <author>Maho Pacheco</author>
      <category>motivation</category>
      <category>engineering</category>
      <category>microsoft</category>
      <category>hacking</category>
    </item>
  </channel>
</rss>
