<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Lab Notes on Jahvon Dockery</title>
    <link>https://jahvon.dev/notes/</link>
    <description>Recent content in Lab Notes on Jahvon Dockery</description>
    <generator>Hugo -- 0.148.1</generator>
    <language>en</language>
    <lastBuildDate>Wed, 06 May 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://jahvon.dev/notes/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>AI as a Development Partner</title>
      <link>https://jahvon.dev/notes/ai-development-partner/</link>
      <pubDate>Wed, 06 May 2026 00:00:00 +0000</pubDate>
      <guid>https://jahvon.dev/notes/ai-development-partner/</guid>
      <description>Reflections on how I&amp;rsquo;ve been using AI as a development partner - what I delegate, what I don&amp;rsquo;t, and what it&amp;rsquo;s produced.</description>
      <content:encoded><![CDATA[<p>Last fall I wrote about <a href="/notes/ai-creative-partner/">using AI as a creative partner</a> after helping with a HGSE module on vibe coding. The conclusion I landed on was careful: AI works best as a scaffold, not a substitute. Use it consciously, review everything, keep your judgment in the loop. I still believe that. But I&rsquo;ve spent the last few months testing what that actually looks like when the output has to live somewhere.</p>
<p>Creative work you experience once. Development work you live in. That difference changes what you need from a partner.</p>
<h2 id="the-bench">The Bench</h2>
<p>When I was at <a href="/tags/recurse/">Recurse Center</a> last summer, I started integrating AI more intentionally into how I build. Not for speed, for learning. I wanted to experiment with architectures I wouldn&rsquo;t normally try, undo decisions cheaply, and see what held up. <a href="/tags/flow/">Flow</a> was the natural workbench. It&rsquo;s my own tool and I know every corner of it.</p>
<p>Over the last few months I&rsquo;ve been building Flow Desktop and refactoring pieces of the core CLI with AI doing a lot of the implementation work. The experience has been different from vibe coding in ways that matter. In the HGSE projects, I was optimizing for something working. Here, I&rsquo;m optimizing for something I can read six weeks later, find when I need it, and build on without second-guessing what&rsquo;s underneath.</p>
<p>That changes what I actually delegate.</p>
<h2 id="the-delegation-model">The Delegation Model</h2>
<p>The architectural decisions stay with me. What the data model looks like, how executables get resolved, where state lives. What I hand off is the implementation of decisions I&rsquo;ve already made. I describe the shape of what I want, review what comes back against that shape, and merge when it aligns. When it doesn&rsquo;t, I say so explicitly.</p>
<p>A concrete example: I&rsquo;ve been building an AI proxy backed by <a href="https://www.cloudflare.com/developer-platform/products/ai-gateway/">Cloudflare AI Gateway</a> that sits across all of my tools. I decided on the architecture, what the proxy needs to do, how it integrates with the <a href="/notes/cloudflare-experience/">Cloudflare platform</a>, what observability I want. AI implemented it. The Cloudflare MCP server made the feedback loop tight enough that I could test and iterate without switching contexts.</p>
<p>What makes this work is having a single place to see everything. Everything I&rsquo;ve configured, discoverable from one surface.</p>
<p><img alt="Flow v2 Terminal UI" loading="lazy" src="/images/flow-v2-tui.gif"></p>
<p>One of the real risks of AI-assisted development is ending up with code you can&rsquo;t navigate. Outputs that don&rsquo;t connect to anything, a project that sprawls in ways you can&rsquo;t audit. The workspace model keeps that from happening. I know where things live because I designed where they live.</p>
<p>I&rsquo;ve also started using AI to enrich Flow itself, generating executable metadata, adding descriptions and tags, making the library more useful as it grows. Flow has an MCP server, so AI tools can interact with it directly. Watching an AI tool work with Flow rather than just producing files has been one of the more interesting parts of this.</p>
<p>Licklider&rsquo;s framing from the last post still holds here. Set the goals, determine the criteria, perform the evaluations. That&rsquo;s still your job. What&rsquo;s changed is my confidence in what I can hand off once those things are set.</p>
<h2 id="what-it-produced">What It Produced</h2>
<p>The review and iterate phase is where the real work happens. AI gets you to a first draft faster. Whether that draft is right is still a judgment call only you can make.</p>
<p>A few months of this produced Flow v2 and something I&rsquo;ve been sitting on: <a href="https://mochiexec.io">Mochi</a>. Development workflows have a way of becoming invisible. They exist, they&rsquo;re just not anywhere you can see them. It&rsquo;s a local-first dev ops dashboard built on Flow. Point it at a directory and it finds your development scripts and automations, turns them into a unified, AI-enriched dashboard. No cloud, no accounts, works with whatever you&rsquo;re already running.</p>
<p><img alt="Mochi Executables View" loading="lazy" src="/images/mochi-executables.png">
<em>Executables view. Everything Mochi found across my workspaces, tagged and filterable.</em></p>
<p>Still early. If it sounds useful, the waitlist is at <a href="https://mochiexec.io">mochiexec.io</a>.</p>
<p>I&rsquo;m more convinced than I was last fall that the gap worth closing isn&rsquo;t between what AI can produce and what you can prompt. It&rsquo;s between what AI produces and what you actually understand. Building in a system you designed is one way to stay honest about that.</p>
]]></content:encoded>
    </item>
    <item>
      <title>From Cloud Native to Serverless with Cloudflare</title>
      <link>https://jahvon.dev/notes/cloudflare-experience/</link>
      <pubDate>Sat, 28 Mar 2026 00:00:00 +0000</pubDate>
      <guid>https://jahvon.dev/notes/cloudflare-experience/</guid>
      <description>Notes from a few months building on Cloudflare Workers after years of Kubernetes.</description>
      <content:encoded><![CDATA[<p>Over the last couple of months, I&rsquo;ve been building a few projects on Cloudflare Workers, and it&rsquo;s been a fun technology shift for me. Developing TypeScript applications on serverless infrastructure after several years of Go and Kubernetes has brought me lots of interesting challenges and opportunities. In many ways, it feels like the opposite of what I&rsquo;ve done with k8s. Infrastructure and component connections (bindings) are managed in a single configuration file instead of sprawling YAML files. Instead of working with containerized microservices and operators, I have to build light processes invoked through in-code APIs. The constraints are different, but working within them has given me a greater appreciation for the power of Kubernetes while also making me grow attached to the seamless developer experience of the Cloudflare platform.</p>
<p><img alt="Cloudflare diagram" loading="lazy" src="/images/cloudflare.png"></p>
<p>The diagram above is roughly how a typical architecture with my most-used patterns comes together. It took a few annoying and painful lessons to land here, but the platform&rsquo;s binding model nudged me in the right direction. Workers are small by design, and the bindings gave me just what I needed to compose them into bigger patterns.</p>
<p>At the core of any architecture is communication, and service bindings and queues work insanely well. I use service bindings when I need fast, direct calls from one worker to another. In my Wrangler config, I just add:</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span>[[services]]
</span></span><span style="display:flex;"><span>binding = <span style="color:#b2c98f">&#34;BOUND_SERVICE&#34;</span>
</span></span><span style="display:flex;"><span>service = <span style="color:#b2c98f">&#34;my-worker&#34;</span>
</span></span></code></pre></div><p>Then in code I can easily send a request like this, with no HTTP overhead:</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#e67e80">const</span> request <span style="color:#7a8478">=</span> <span style="color:#e67e80">new</span> Request(<span style="color:#b2c98f">&#39;api/v1/data&#39;</span>, { method<span style="color:#7a8478">:</span> <span style="color:#b2c98f">&#39;GET&#39;</span> });
</span></span><span style="display:flex;"><span><span style="color:#e67e80">const</span> response <span style="color:#7a8478">=</span> <span style="color:#e67e80">await</span> env.BOUND_SERVICE.fetch(request);
</span></span></code></pre></div><p>This might seem simple, but coming from a k8s background it&rsquo;s a meaningful shift. At a previous role we spent real time and infrastructure trying to solve service discovery; trying to figuring out which services talk to each other, which host to use per environment, keeping that visible and manageable across teams. We end up reaching for tools just to answer the question &ldquo;who calls who.&rdquo; With service bindings, the answer lives right in the config. It&rsquo;s a couple of lines, and those lines double as documentation of your service communication graph without any extra infrastructure to maintain.</p>
<p>I use queues when failure actually matters. Anything I need to retry, delay, or handle gracefully goes through a queue. In k8s I would have reached for another tool like Kafka or SQS for this, which means more infrastructure to provision, configure, monitor, and reason about. Here it&rsquo;s all in the Wrangler config:</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span><span style="color:#859289;font-style:italic"># In the producer&#39;s wrangler.toml</span>
</span></span><span style="display:flex;"><span>[[queues.producers]]
</span></span><span style="display:flex;"><span>queue = <span style="color:#b2c98f">&#34;message-queue&#34;</span>
</span></span><span style="display:flex;"><span>binding = <span style="color:#b2c98f">&#34;MESSAGE_QUEUE&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#859289;font-style:italic"># In the consumer&#39;s wrangler.toml</span>
</span></span><span style="display:flex;"><span>[[queues.consumers]]
</span></span><span style="display:flex;"><span>queue = <span style="color:#b2c98f">&#34;message-queue&#34;</span>
</span></span><span style="display:flex;"><span>max_concurrency = <span style="color:#d699b6">5</span>
</span></span><span style="display:flex;"><span>max_batch_size = <span style="color:#d699b6">3</span>
</span></span><span style="display:flex;"><span>max_batch_timeout = <span style="color:#d699b6">5</span>
</span></span><span style="display:flex;"><span>max_retries = <span style="color:#d699b6">3</span>
</span></span><span style="display:flex;"><span>dead_letter_queue = <span style="color:#b2c98f">&#34;message-dlq&#34;</span>
</span></span></code></pre></div><p>Then in code I just end up with code blocks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#859289;font-style:italic">// Sending messages
</span></span></span><span style="display:flex;"><span><span style="color:#859289;font-style:italic"></span><span style="color:#e67e80">const</span> msg <span style="color:#7a8478">=</span> { key<span style="color:#7a8478">:</span> <span style="color:#b2c98f">&#39;value&#39;</span> };
</span></span><span style="display:flex;"><span><span style="color:#e67e80">await</span> env.MESSAGE_QUEUE.send(msg);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#859289;font-style:italic">// Processing message
</span></span></span><span style="display:flex;"><span><span style="color:#859289;font-style:italic"></span><span style="color:#e67e80">export</span> <span style="color:#e67e80">default</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#e67e80">async</span> queue(batch, env, ctx)<span style="color:#7a8478">:</span> Promise&lt;<span style="color:#7a8478">void</span>&gt; {
</span></span><span style="display:flex;"><span>		<span style="color:#e67e80">for</span> (<span style="color:#e67e80">const</span> message <span style="color:#e67e80">of</span> batch.messages) {
</span></span><span style="display:flex;"><span>			console.log(message.key);
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>No additional tooling. The queuing system is just there and the resilience story is config rather than code. That trade-off keeps showing up with Cloudflare and it&rsquo;s one of the things I&rsquo;ve genuinely enjoyed about working on the serverless side of things.</p>
<p>One thing to note is that workers run in a V8 sandbox with strict resource limits. While the resource limits are real, they are manageable once you stop fighting them. The pattern I kept coming back to was breaking long-running processes into chunks and passing a continuation token through queue messages or service calls. This essentially means checkpointing work. In some cases, that involves persisting temporary data to R2 or KV so nothing blows its budget in a single execution.</p>
<p>The other binding that comes up when I want to avoid some of the limitations of the worker runtime is containers (currently in beta). Some of the limits can be configured (and increased with a Workers paid subscription) a bit, but the runtime can&rsquo;t. When I needed to run workloads that didn&rsquo;t neatly fit into the model, a container binding let me attach an image with its own runtime and call into it the same way I call into service bindings. For example, if you need image processing with a library like Sharp for compression or transformation, you can&rsquo;t run that inside a worker but a container binding solves it cleanly.  It uses a slim wrapper for passing environment variables and spinning up instances in the bound worker&rsquo;s code.</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#859289;font-style:italic">// The container creates a &#34;Durable Object&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#859289;font-style:italic"></span><span style="color:#e67e80">export</span> <span style="color:#e67e80">class</span> MyContainer <span style="color:#e67e80">extends</span> Container {
</span></span><span style="display:flex;"><span>	defaultPort <span style="color:#7a8478">=</span> <span style="color:#d699b6">8080</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	envVars <span style="color:#7a8478">=</span> {
</span></span><span style="display:flex;"><span>		ENV_KEY1: <span style="color:#dbbc7f">env.ENV_KEY1</span> <span style="color:#7a8478">||</span> <span style="color:#b2c98f">&#39;&#39;</span>,
</span></span><span style="display:flex;"><span>		ENV_KEY2<span style="color:#7a8478">:</span> <span style="color:#b2c98f">&#39;hello world&#39;</span>
</span></span><span style="display:flex;"><span>	};
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#859289;font-style:italic">// Send requests to the container
</span></span></span><span style="display:flex;"><span><span style="color:#859289;font-style:italic"></span><span style="color:#e67e80">const</span> container <span style="color:#7a8478">=</span> env.CONTAINER.getByName(<span style="color:#b2c98f">&#39;instance-1&#39;</span>);
</span></span><span style="display:flex;"><span>container.fetch(<span style="color:#b2c98f">&#39;api/v1/action&#39;</span>, {
</span></span><span style="display:flex;"><span>	method<span style="color:#7a8478">:</span> <span style="color:#b2c98f">&#39;POST&#39;</span>,
</span></span><span style="display:flex;"><span>	body: <span style="color:#dbbc7f">JSON.stringify</span>(request),
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>Containers (and <a href="https://developers.cloudflare.com/durable-objects/">durable objects</a>, in general) provide a nice escape hatch. The rest of your architecture stays serverless and you only reach for a container when the runtime genuinely needs it.</p>
<p>The constraint that keeps biting me is D1&rsquo;s query limitations, especially the 100-parameter limit on batch queries. Every time my data models grew, I ran into sneaky bugs related to this. The fix is just chunking batches so that I never pass in so many parameters, but it took a few rounds before it became second nature. There are many other platform <a href="https://developers.cloudflare.com/workers/platform/limits/">limits</a> worth knowing too, but I rarely hit them when following the patterns that help me get around those other challenges.</p>
<p>I&rsquo;ve built on a lot of platforms where infrastructure problems and application problems are tangled together. On Workers, they&rsquo;re largely separate. It&rsquo;s been interesting working in a system where the constraints shift from operational to architectural.</p>
<p>There are definitely still some rough edges when working with Cloudflare, but in the short time I&rsquo;ve been using it, I&rsquo;ve also seen some great enhancements made to the ecosystem. Generally, I&rsquo;m left to think about the design of what I&rsquo;m building rather than how to keep it running. I like to pair it with tools like <a href="https://github.com/drizzle-team/drizzle-orm">Drizzle</a>, <a href="https://hono.dev/">Hono</a>, and <a href="https://localflare.dev/">Localflare</a> to improve my developer experience even more. Between the Wrangler CLI, MCP integration, GraphQL API, and solid documentation, Cloudflare provides a great suite of tools for extending architectures quickly with the help of coding assistants.</p>
<p>Coming from k8s, the biggest adjustment isn&rsquo;t the TypeScript or the serverless model; it&rsquo;s recalibrating how much infrastructure you actually need to think about. My home server still runs Kubernetes and I still genuinely enjoy that flexibility; being able to drop in open source software and wire it together however I want is something Cloudflare can&rsquo;t match. But for pipelines where I just need things to run, Workers is hard to beat. The mental shift is realizing those aren&rsquo;t competing opinions, they&rsquo;re just different problems. I came in expecting to feel constrained. I left thinking more carefully about what I actually need to own.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Building Boston HERC&#39;s Website, Twice</title>
      <link>https://jahvon.dev/notes/herc-journey/</link>
      <pubDate>Tue, 23 Dec 2025 00:00:00 +0000</pubDate>
      <guid>https://jahvon.dev/notes/herc-journey/</guid>
      <description>My experience redesigning Boston HERC&amp;rsquo;s website to reflect their growth and impact supporting first-generation students in the Boston area.</description>
      <content:encoded><![CDATA[<p>One of the most rewarding projects that I&rsquo;ve had the chance to work on this past year was one that allowed me to work closely with one of my favorite organizations, <a href="https://www.bostonherc.org/">Boston Higher Education Resource Center (HERC)</a>. The Boston HERC serves first-generation youth of color, providing support from 7th grade through postsecondary success. I first connected with HERC in 2019 through <a href="https://www.catchafire.org/">Catchafire</a> - a platform that matches skilled volunteers with mission-driven organizations. At the time, they needed help migrating from a single page Wix site to WordPress.</p>
<p>I didn&rsquo;t quite know what I was getting myself into, being early in my career and without any WordPress experience. The project ended up being much more than a simple migration but it was a great collaboration. I spent about 9 months working on my first project with the HERC team. We came up with a much more beautiful and informative site that they could feel proud to share with their community and donors. I was initially drawn to the HERC because of my own experience in high school but fell in love with the organization even more as I got to learn about their history and impact.</p>
<h3 id="staying-involved">Staying involved</h3>
<p>Naturally, I didn&rsquo;t want my involvement to end there. I&rsquo;ve continued to help them with their WordPress site over the years, mostly with small updates as new reports and events happened. I&rsquo;ve also had many chances to engage with the students that they support through panel discussions, STEM presentations, and a brief mentorship match. I was honored to receive their Make a Difference Award at this past May&rsquo;s annual celebration.</p>
<p>Since the first site design project, they&rsquo;d grown to reach over 1,600 students annually across 13 partner schools in 3 districts. They&rsquo;d launched an Alumni Success Program, expanded beyond Boston into Revere and Chelsea, and built up an even greater team of leaders and coaches empowering low-income youth across the Boston area. Unfortunately, their website was still telling the story of who they were, not who they&rsquo;d become. That led me to reaching out to offer some of my time to give it a complete refresh!</p>
<h3 id="redesign-goals">Redesign goals</h3>
<p>Looking through their existing site statistics, old and new content, and chatting with their team, I realized this wasn&rsquo;t just about updating copy and swapping out photos. Boston HERC needed their digital presence to reflect their empowering, student-first approach to supporting first-generation college students.</p>
<p>My focus for this redesign was on three key areas:</p>
<p><strong>Storytelling that matches their identity</strong>: Softening the overall feel of the site that allows the stories of their community and students to shine through more naturally.</p>
<p><strong>Data-driven messaging</strong>: Integrating their impressive outcomes data throughout the site, not buried in an annual report, but woven into the narrative of what makes Boston HERC different.</p>
<p><strong>Streamlined user journeys</strong>: Whether someone was a prospective student, a potential funder, or a school administrator looking to partner, the site needed to quickly communicate Boston HERC&rsquo;s value proposition and next steps.</p>
<p>There&rsquo;s something humbling about helping an organization that&rsquo;s been steadily doing the work for over 25 years. I admire how Boston HERC has been laser-focused on their mission to equip first-generation youth to access and thrive in higher education.</p>
<h3 id="technical-approach">Technical approach</h3>
<p>As a developer, I often get caught up in technical complexity and can be guilty of overengineering architecture at times. My latest WordPress administration work that I did for HERC was a nice way to challenge that inclination. My first time around, I used <a href="https://underscores.me/">underscores</a> as the base for the theme I created but spent a lot of time tweaking things, especially as I tried to make the site responsive for smaller devices. I had to write a lot of HTML, CSS, and JavaScript for the theme and had to figure out some of the oddities of WordPress PHP for some customizations I needed.</p>
<p>For my second time theming this site, I decided to lean into existing technologies, themes, widgets, and extensions so that it&rsquo;s easier for the HERC team to maintain and for me to make adjustments, without having to spend too much time in the weeds. I decided to use an <a href="https://elementor.com/">Elementor</a> theme as the basis of the latest site. I still had to inject in the HERC brand, create some custom assets, and figure out how I wanted to structure the pages but it was a much smoother experience this time.</p>
<p>I wanted to make sure that I was using my time on the most impactful side of this work: making sure their story is told effectively. When Boston HERC&rsquo;s website better reflects their sophistication and impact, it helps them reach more students, attract more funding, and partner with more schools. This project was a great reminder of how technology can serve a mission, not the other way around.</p>
<p><strong>You can see the live transformation at <a href="https://bostonherc.org/">bostonherc.org</a>.</strong></p>
<p><em>If their mission speaks to you too, consider <a href="https://www.bostonherc.org/get-involved/give/">giving to this great organization</a>!</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>AI as a Creative Partner</title>
      <link>https://jahvon.dev/notes/ai-creative-partner/</link>
      <pubDate>Wed, 19 Nov 2025 00:00:00 +0000</pubDate>
      <guid>https://jahvon.dev/notes/ai-creative-partner/</guid>
      <description>An essay reflecting on my time using GenAI as a creative partner for a HGSE course that I was a teaching fellow for.</description>
      <content:encoded><![CDATA[<p>Since the start of the year, I&rsquo;ve been on a journey with learning about and with Large Language Models, settling into new AI tooling workflows, and reflecting on how these technologies have been showing up in my work. It&rsquo;s been quite impossible to avoid the constant AI buzz, so I wanted to figure out if my earlier AI skepticism was misplaced. I would only delegate teeny tiny tasks and easily confirmable questions to these systems. My time at Recurse Center this past summer accelerated that exploration even more. I tried several intentional experiments with a range AI development tools and processes. It gave me my first experiences with vibe coding during a weekly interest group that had formed. <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>My own position has begun to develop even more over the last six weeks, as I served as a Teaching Fellow for a Harvard Graduate School of Education module on using generative AI as a creative partner. It followed a project-based structure where students sought to build vibe coded apps to respond to a weekly prompt. Build something that&hellip; &ldquo;makes your life easier&rdquo;, &ldquo;invites play&rdquo;, &ldquo;answers a question&rdquo;, etc. The studio group that I supported included 15 students coming from a variety of backgrounds but many have never coded or used AI tools, from grade school educators to EdTech entrepreneurs they all shared a similar desire of getting their hands dirty with AI so that they can learn how they can apply it with the work that they want to do. We used tools like Replit, Claude Code, Google Colab, and Figma Make to play with AI in a reflective space. Alongside each session and through 1:1 conversations, I got to engage in lots of thoughtful discussions about ideating, prompting, iterating, societal impacts of AI, limitations of the current tools, our routine usage of these tools, and much more. I deeply engaged in the coursework, not only as a teacher, but as a fellow learner.</p>
<h2 id="what-we-built">What We Built</h2>
<h3 id="the-collaborative-illusion">The Collaborative Illusion</h3>
<p>For the first project, the class was tasked with building something that tells a story. I decided to use Claude Code to create an interactive version of The Three Little Pigs. I didn&rsquo;t really have specific technologies in mind for this project so I just sent a straightforward prompt that described that I wanted animated visuals that matched the story as the viewer worked through it. I was inspired by the gentle animations of <a href="https://beta.hearingbirdsong.com/">Hearing Birdsong</a> so I tried to describe my experience with that site as a foundation for how I wanted my story to be. Claude&rsquo;s response to that design was far from what I imagined. I went back and forth a few times, trying to see if I could iterate to improve the size and positioning of the text, interactive actions, animations, and design elements but I was left unsatisfied overall.</p>
<p>I knew that Claude Code does not generate images but I would have loved to see it admit defeat. Explicitly tell me that it could not create a visually appealing animations without its current set of tools and assets. Or tell me that it made the wrong decision when it decided on the initial tech stack after getting more information from me. Instead, when I described what I wanted the pigs and homes to be modeled as, it stuck with unsatisfying SVG representations.</p>
<p>Reading about what Joseph Weizenbaum wrote in <em>Contextual Understandings by Computers</em> about ELIZA, his 1960s chatbot, a few weeks later reminded me of this experience:</p>
<blockquote>
<p>One of the principle aims of the DOCTOR program is to keep the conversation going&ndash;even at the price of having to conceal any misunderstandings on its own part.</p></blockquote>
<p>These modern AI systems seem to operate similarly - they&rsquo;re optimized to maintain the illusion of understanding and expertise rather than honestly calling out their limitations. Claude kept generating code, stating that it was making progress even though the questions that I continued to ask were clearly stating otherwise. I wasn&rsquo;t too surprised by this given my previous experiments with AI but many students struggled with this phenomena.</p>
<h3 id="drawing-the-line">Drawing the Line</h3>
<p>The fifth week of the course, we focused on building games! As a kid, I dreamed of creating my own video games. I ended up taking a different path with my software career so it felt a bit too ambitious for me to try to jump into as a side project. I decided to put Claude Code to test again for this. My vision was to create a game that combined two games that I played when I was a kid: Pokemon and Neopets. (Imagine being able to select a Neopet to go up against other wild Neopets) It was this week that I really started to feel the need for much more collaborative development with Claude. In the first three weeks, I stuck mostly to prompt-review-reprompt cycles but this week I was consistently unsatisfied with what was being created.</p>
<p>I decided to take a look at the code that was being written, edited some bits, and asked for clarification. Then eventually, I was able to tell it explicitly how I wanted it to implement some of the features that I needed. I also had to take a much more active role in getting the aesthetics to align with what I wanted. I did the work of researching assets that I can pull in, colors and fonts that I should use, and crafted detailed explanations for the placement of some elements.</p>
<blockquote>
<p>In the anticipated symbiotic partnership, men will set the goals, formulate the hypotheses, determine the criteria, and perform the evaluations. Computing machines will do the routinizable work that must be done to prepare the way for insights and decisions in technical and scientific thinking.</p>
<p><em>Man-Computer Symbiosis, J. C. Licklider</em></p></blockquote>
<p>Licklider&rsquo;s explanation of how he viewed the relationship between man and computer in his 1960 paper felt spot on in how my experience went. I was doing exactly that: formulating what &ldquo;good Pokemon-meets-Neopets gameplay&rdquo; meant. This productive collaboration only emerged when I stopped treating the AI as capable of independent creative judgment and started treating it as Licklider envisioned.</p>
<h2 id="what-we-uncovered">What We Uncovered</h2>
<h3 id="vibe-coding-in-practice">Vibe Coding in Practice</h3>
<p>I loved seeing the joy and excitement that spread across the room as students worked on and shared their projects. But I really appreciated the moments of shared frustration that brought up thoughtful questions as we wrestled with the limitations of using AI as a creative partner. Non-technical creators now have the ability to apply code to problems in their own lives and domains; in a way that was much more out of reach before. It was quite refreshing hearing how students want to use vibe coding to do things like spinning up interactive prototypes for professional development trainings they&rsquo;re building, teaching other entrepreneurs the strengths and limitations of AI use in the social innovation space, simplify the creation of classroom worksheets and activities, and much more.</p>
<p>To give you a sense of what <em>I</em> was able to create with AI, I vibe coded this interactive portfolio:</p>
<iframe src="https://vibes.jahvon.dev" width="100%" height="600px" frameborder="0"></iframe>
<p>We hear that the power is in the prompt but, for me, the whole process matters. I&rsquo;ve learned that you can come with a great, detailed prompt but without an understanding of what&rsquo;s possible and where AI should create versus where you should intervene, you&rsquo;ll end up disappointed or at risk. While vibe coding lowers the barrier to entry for creating, it doesn&rsquo;t guarantee that you won&rsquo;t get lost once you&rsquo;re inside. It can do very well with applying simple, common applications of code but fall apart in the obscure cases. And without AI having a full understanding of what you are intending to create and you having an idea of what it is creating, it can lead you down paths that may be harmful and unproductive.  A student shared how it has an &ldquo;addicting&rdquo; effect since you can instantly see an idea realized. As someone who has the understanding of the code these vibe coded projects produced, I would be hesitant to use it blindly for anything that requires care and attention. Especially not without some careful review and collaborative implementing&hellip; but I don&rsquo;t think it&rsquo;s vibe coding at that point.</p>
<h3 id="the-efficiency-trap">The Efficiency Trap</h3>
<p>A lot of the hype that I see with AI is around how much more efficient it makes people. I had many conversations with students about the potential for AI to take away jobs, weaken relationships, increase dependency on technology, and kill the individual learning and creative process.</p>
<p>Kate Crawford argues in <em>The Atlas of AI</em> that we need to ask &ldquo;what is being optimized, and for whom, and who gets to decide.&rdquo; When we optimize for speed in creating apps or generating content, what are we not optimizing for? Crawford points out that &ldquo;the true costs of this extraction is never borne by the industry itself&rdquo; - not the environmental costs of training models, not the labor costs of the workers who label data, not the costs to students whose critical thinking declines from over-reliance on generated answers.</p>
<p>The efficiency gains are real - I built 6 functional prototypes in hours that would have taken me weeks. But the costs are externalized: to my own learning, to the development of judgment and perspective, to the practice and growth of skills like problem solving.</p>
<h3 id="designing-dependency">Designing Dependency</h3>
<p>In one of my reading discussion, we talked about how companies like OpenAI, Google, and Anthropic are building LLMs with features that mimic human connection: memories of past conversations, empathetic language, customizable personalities, approachable voices. Someone shared how ChatGPT had referenced her previous chat about being sick in a completely unrelated conversation - unprompted, it checked in on her health. While the gesture may feel nice, it raised an unsettling question: should we be designing machines to provide emotional connection?</p>
<p>Crawford warns that AI systems &ldquo;are ultimately designed to serve existing dominant interests.&rdquo; What interests does artificial empathy serve? I think that the goal is to optimize for engagement metrics, not genuine human wellbeing - keeping users returning to the platform, deepening dependence on the system. These features don&rsquo;t seem to be about about connection; they&rsquo;re about retention.</p>
<p>I&rsquo;ve heard stories of people ending relationships based on the AI&rsquo;s advice or seeking emotional support primarily from chatbots. When we find ourselves turning to ChatGPT for thoughts on deeply personal matters, we should ask: Does it have the full context of our lives like a close friend would? Does it challenge us when needed, like a parent might? Can we trust its guidance when it doesn&rsquo;t know what we&rsquo;re not sharing?</p>
<p>Crawford describes AI as &ldquo;both embodied and material, made from natural resources, fuel, human labor, infrastructures, logistics, histories, and classifications.&rdquo; But these systems fundamentally lack what makes human connection meaningful: they have no stakes in our life, no shared history beyond collected data, no capacity to be changed by knowing us. A chatbot remembering you were sick is pattern-matching engineered to feel like care.</p>
<p>Sure, we may reach a point where AI convincingly simulates every feature of human relationship. These aspects may make the creative process feel more personal, but that still leaves actual messy, complicated, but irreplaceable connections at risk.</p>
<h2 id="a-working-philosophy">A Working Philosophy</h2>
<p>For quick MVPs and non-critical prototypes, these tools are genuinely useful. But they can&rsquo;t replace pair programming with a colleague who asks why you&rsquo;re solving the problem that way, whiteboarding with your team where someone sketches a better approach, or independent research that builds understanding from the ground up. The Pokemon-Neopets game required me to step in - researching assets, making aesthetic decisions, explicitly directing implementation. That&rsquo;s where I learned something. As one Recurser put it, LLMs are like e-bikes: great for getting somewhere quickly, but if your goal is to become stronger, they won&rsquo;t help you with that. I found most of the value with working with these tools when I critically engaged with what&rsquo;s being generated during the review and iterate phase.</p>
<p>A student told me she&rsquo;s learned to change her expectations when working with AI tools - we start with grand ideas of what they can do, but these systems lack the qualities that enable human imagination and creation. Earlier this year, I saw this work well when a friend asked if I could help him learn some Python. He was curious about automating data analysis that he does as a scientist in biotech. I decided to use Claude to help me craft a curriculum and some exercises for us to work through. After gathering some more information about the data formats, goals, and background for his work; we actually ended up with a decent set of lessons that got him comfortable with writing Python and using numpy and pandas to help with some tasks. When I sent him off on his own, he had both tools and understanding.</p>
<h3 id="ai-as-a-learning-partner">AI as a Learning Partner</h3>
<p>That difference between my earlier experience with AI and my more recent vibe coding experiences is in the way AI is collaboratively used as a scaffold for learning and creating versus replacement for it. LLMs risk creating a gap between the edge of what you can produce and what you can understand. I could see AI working as a much better learning partner than a creative partner. This requires more investment upfront from us but pays off in genuine capability rather than dependency. I&rsquo;ve started including explicit process instructions in my prompts: &ldquo;Before writing any code, summarize what you&rsquo;re about to do and ask for confirmation.&rdquo; &ldquo;Admit when questions are ambiguous.&rdquo; Unfortunately, some LLMs routinely ignore these instructions so you still have to be independently vigilant.</p>
<p>I&rsquo;ll keep using AI tools, but with clearer boundaries. For rapid prototyping where I need speed over quality. For handling boilerplate so I can focus on interesting problems. Always understanding that output requires review, refinement, and judgment only I can provide. This course reinforced something I suspected: the most important parts of learning and creating can&rsquo;t be automated, not because AI will never be technically capable, but because we must build our own mental structures. LLMs can give fast answers, but only you can determine which questions you care about, and which answers are meaningful. Being a teaching fellow for this module showed me that the students who thrived weren&rsquo;t the ones who generated the most code - they were the ones who asked the best questions, challenged the outputs, and built understanding through iteration. I&rsquo;m carrying forward a position, not of rejection or uncritical embrace, but of conscious engagement with these tools as supplements to my creative capability, never substitutes for it.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Check out RC&rsquo;s <a href="https://www.recurse.com/blog/191-developing-our-position-on-ai">position on AI</a> that dropped during my time in batch. The sentiments around balancing &ldquo;shipping mode&rdquo; and &ldquo;learning mode&rdquo; when considering AI usage really resonated with me and the experience that I had during that time.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>return recurse(): Summer of Building and Learning</title>
      <link>https://jahvon.dev/notes/rc-return-statement/</link>
      <pubDate>Tue, 09 Sep 2025 00:00:00 +0000</pubDate>
      <guid>https://jahvon.dev/notes/rc-return-statement/</guid>
      <description>What I built and learned during 12 weeks of focused development at the Recurse Center.</description>
      <content:encoded><![CDATA[<p>I wrapped up my batch at <a href="https://www.recurse.com/scout/click?t=420ecb9ef5810758f6fe8dec816d80a8">Recurse Center</a> a few weeks ago and wanted to capture what I worked on during those 12 weeks.
RC gave me the space I needed to dive deep into a passion project, pick up new technologies, and push my
comfort zone across many areas of software engineering.</p>
<h3 id="learning-highlights">Learning highlights</h3>
<p><strong>Collaborative learning through pairing</strong> - Gained exposure to a diverse set of projects and development approaches. I didn&rsquo;t always
understand the technologies or projects I paired on, but they often provided inspiration for improving my own work or workflows. Notable
pairing sessions included a recipe management app with OpenAI integration, a BPE tokenizer implementation, and programming language development.</p>
<p><strong>Teaching through presentation</strong> - Regular demos of flow progress and architectural decisions solidified my understanding and gave me
practice explaining complex technical concepts to others. Take a look at my <a href="https://docs.google.com/presentation/d/11WBdY8pZ5IPJkBGb9A9pZIWUTCa9D2RAJaeNHPXNIQg/edit?usp=sharing">final presentation deck</a>
for the overview that I gave on flow&rsquo;s composable workflows!</p>
<p><strong>Discovering my learning patterns</strong> - After a few weeks of experimenting with time management, I settled on a three-day project focus
while leaving space for exploration. Using flow itself as a platform for trying new technologies safely turned out to be an effective
learning strategy.</p>
<p><strong>Balancing focus and community</strong> - The biggest challenge was finding rhythm between deep work and community engagement - there were so
many interesting things happening at RC. Writing regular check-ins and reading others&rsquo; updates helped me reflect and adjust that balance as desired.</p>
<h3 id="core-project">Core project</h3>
<p>As I described in <a href="/notes/rc-6-week-flow/">6 Weeks of flow at Recurse Center</a>, I decided to expand my long-running side project <em>flow</em> as my
main focus. A little more than half of my flow work fell into these four major efforts, with the remainder spent on bug fixes and UX
improvements throughout.</p>
<p><strong>flow CLI v1.0 release</strong> - Stabilized platform with comprehensive testing, v2 of the integrated secrets vault, major documentation
updates, and CI integration via custom <a href="https://github.com/marketplace/actions/flow-execute">GitHub Action</a></p>
<p><strong>Desktop app POC</strong> - Built proof-of-concept with <a href="https://github.com/tauri-apps/tauri">Tauri</a>, establishing CLI-as-source-of-truth architecture for upcoming first release</p>
<p><strong>Executable generation</strong> - Added parsers for makefile, package.json, and docker-compose to streamline workflow migration</p>
<p><strong>MCP server</strong> - Built <a href="https://modelcontextprotocol.io/docs/getting-started/intro">Model Context Protocol</a> integration enabling AI tools to understand flow workspaces and executables natively</p>
<p>Check out my new <a href="/architecture/flow/">architecture doc</a> for more details on how I&rsquo;ve been building this local-first developer automation platform. To continue on with the &ldquo;learning generously&rdquo; mindset, I plan to keep it updated as the architecture evolves.</p>
<h3 id="honorable-mentions">Honorable mentions</h3>
<p><strong>Dev environment exploration</strong> - Tried various changes to my development editors and workflows, including AI-enhanced IDEs,
terminal-based applications, and improvements to local project and dotfile organization. This also included deeper integrations of
flow into my productivity, development, and operation workflows as I began to use it to standardize workflows across my side-projects.</p>
<p><strong>&ldquo;Impossible&rdquo; experiments</strong> - Single-day projects at the edge of my abilities, including a CGo process monitor using C for system process
information and a container runtime with runc. Also explored a WebAssembly plugin system for flow - not as impossible as it seemed but
informative for future feature planning.</p>
<p><strong>Creative coding</strong> - Completely new domain I fell into through RC events. These sessions became a refreshing creative outlet, including
mini projects with p5.js, tone.js, Motion Canvas, and the Python Imaging Library. Check out <a href="https://codepen.io/jahvon/pen/pvJXJjv">this Codepen</a> for an example of one of my creations.</p>
<p><strong>Vibe coding</strong> - Exploration of spinning up complete applications with AI development tools. These weekly sessions gave me better
appreciation of AI-assisted coding and helped me understand its current limitations. I&rsquo;ve since been vibe coding a few small apps
for my home lab. It&rsquo;s also been cool to also see how vibes + flow MCP can produce some neat, standardized workflows.</p>
<p><strong>Community programming</strong> - Beyond the creative and vibe coding sessions, I explored topics well outside my main focus through these
community activities. System design discussions, a workshop on building Obsidian plugins, and weekly non-programming presentations are
just a few that kept me curious about areas I wouldn&rsquo;t encounter naturally. As a &ldquo;never-graduated&rdquo; alum, I&rsquo;m looking forward to continuing
to participate whenever time allows.</p>
<p><strong>WordPress project</strong> - Pro bono work for nonprofit Boston HERC. Started before RC but got more time to work on this and
launched the first phase of the redesign of their core pages at <a href="https://bostonherc.org">bostonherc.org</a>. It was interesting finding
ways to apply my evolving learning style to evaluating and picking up newer, no-code frameworks like Elementor.</p>
<hr>
<p>My time at RC ended up giving me much more than I was hoping for. I got dedicated time to tackle ambitious ideas
I&rsquo;d been putting off for a while and learn new-to-me technologies in a supportive environment. The collaborative culture
pushed me to share my work regularly and learn from other skilled builders working on completely different problems.
As I move into the next phase of my career, I&rsquo;m carrying forward not just new technical skills but a better understanding
of how I learn best and what gets me excited about software engineering.</p>
<script async defer src="https://www.recurse-scout.com/loader.js?t=420ecb9ef5810758f6fe8dec816d80a8"></script>
]]></content:encoded>
    </item>
    <item>
      <title>6 Weeks of flow at Recurse Center</title>
      <link>https://jahvon.dev/notes/rc-6-week-flow/</link>
      <pubDate>Mon, 30 Jun 2025 00:00:00 +0000</pubDate>
      <guid>https://jahvon.dev/notes/rc-6-week-flow/</guid>
      <description>An update on my journey tackling developer tool chaos with a personal automation platform. 6 weeks of building desktop apps, cryptographic vaults, and feature planning at Recurse Center.</description>
      <content:encoded><![CDATA[<p>I&rsquo;ve been thinking a lot about developer tooling and the idea of a &ldquo;personal developer platform&rdquo; - something that I could adapt to aid how <em>I</em> want to develop. Modern development can feel like tool chaos at times - we&rsquo;re juggling package managers, test runners, linters, deployment scripts, language-specific tooling, and dozens of open source CLI tools. Each project accumulates its own collection of scripts and commands that live in different places with different interfaces.</p>
<p>I introduced <a href="https://flowexec.io/">flow</a> in <a href="/notes/forging-flow/">another post</a> a few months ago, but I&rsquo;ve had the amazing opportunity to start a sabbatical at the <a href="https://www.recurse.com/scout/click?t=420ecb9ef5810758f6fe8dec816d80a8">Recurse Center</a>, where I&rsquo;ve been able to think more deeply about the problems I was solving and the technologies I wanted to learn about! As I mentioned in that post, flow has been my &ldquo;learning platform&rdquo; over the last 2 years and I knew I wanted to take it a step further at Recurse.</p>
<p>I&rsquo;m at the halfway point of my time at RC and am excited to share how my first 6 weeks have been on my main coding project.</p>
<h2 id="flow-desktop">flow desktop</h2>
<p>I&rsquo;ve been itching to work on a frontend project for a while now. While building the flow TUI library, I had lots of fun thinking about how I could create a good experience through visuals and layout. <a href="https://github.com/charmbracelet/bubbletea">Bubble Tea</a> has been fun to use here, but I&rsquo;ve wanted to do some UI development with more possibilities. Doing this in the &ldquo;browser&rdquo; and using new-to-me technologies sounded like a great plan.</p>
<p>Coming into RC, this was something I knew I wanted to work on, but my excitement grew as I started to learn about the fascinating world of frontend development through RC pair programming, events, and chats.</p>
<p><strong>Architecture Decision: CLI as Single Source of Truth</strong></p>
<p>Instead of duplicating business logic in my desktop app, I&rsquo;ve been building it as a pure visualization layer over the existing CLI.</p>
<p><img alt="Desktop Architecture" loading="lazy" src="/images/flow-desktop-arch.png"></p>
<p>This felt risky at first - wouldn&rsquo;t spawning processes be too slow? Turns out CLI commands execute pretty fast thanks to some caching I do on the CLI side. The whole round trip feels instant with my current usage.</p>
<p><strong>Tech Stack</strong></p>
<p>All of the resources, pairing, feedback, and individual research I&rsquo;ve done has landed me on the following:</p>
<ul>
<li><strong>Tauri</strong>: Gives me Rust backend + web frontend without Electron&rsquo;s bloat. Bonus that it&rsquo;s an opportunity to learn some Rust.</li>
<li><strong>TypeScript</strong>: I was able to get TS and Rust types generated from the same JSON schema that I use to generate Go code. This has been making development much smoother across the 3 languages that flow now uses.</li>
<li><strong>React &amp; Mantine UI</strong>: VSCode-like components without building everything from scratch. Their <a href="https://mantine.dev/x/spotlight/">Spotlight</a> extension is what sold me - it could be a really cool search and command center for the UI!</li>
</ul>
<p><strong>Demo!</strong></p>
<p>Here is a quick demo of me using my current implementation of the desktop. This shows the workspace and executable viewer/runner in action - you can see me running an executable directly from the UI and playing with the theme picker I prototyped for customization.</p>
<video autoplay loop muted playsinline width="100%">
  <source src="/images/flow-desktop-demo.mp4" type="video/mp4">
</video>
<p>I still have more work to do here, but it&rsquo;s been satisfying seeing my ideas come to life as I pick up these new technologies.</p>
<h2 id="vaults-v2">vaults v2</h2>
<p>I had a couple of pain points with the vault that I initially built for flow. The UX was pretty simple but limiting. I&rsquo;ve also been really wanting a way to integrate my Bitwarden secrets into flow seamlessly. This led me to brainstorm a new design for that feature. I spent my first 2 weeks at RC doing some light research on cryptography with Go, studying how other tools handle secrets, and building a simple POC.</p>
<p>I decided to introduce a &ldquo;provider&rdquo; concept that also improves the experience around having multiple vaults. I currently have an implementation for an updated version of my AES symmetrically encrypted vault, added an Age asymmetric encryption backend, and plan to add a backend for custom CLI-tool vault managers. You can see what I came up with in <a href="https://github.com/jahvon/vault">this repo</a>. From the flow perspective, the experience would be like this:</p>
<p><strong>Creating a vault</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#859289;font-style:italic"># Auto-generates everything for the AES vault</span>
</span></span><span style="display:flex;"><span>flow vault create development
</span></span><span style="display:flex;"><span><span style="color:#859289;font-style:italic"># Create an Age vault with identity generated from age-keygen</span>
</span></span><span style="display:flex;"><span>flow vault create team --type age --recipients key1,key2,key3 --identityFile id.txt
</span></span><span style="display:flex;"><span><span style="color:#859289;font-style:italic"># External needs CLI integration</span>
</span></span><span style="display:flex;"><span>flow vault create bitwarden --type external --interactive
</span></span></code></pre></div><p><strong>Vault Switching as Primary UX</strong></p>
<p>Borrowed the mental model from git/kubectl:</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>flow vault switch development    <span style="color:#859289;font-style:italic"># Like git checkout</span>
</span></span><span style="display:flex;"><span>flow secret <span style="color:#d699b6">set</span> api-key <span style="color:#b2c98f">&#34;dev-123&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>flow vault switch production
</span></span><span style="display:flex;"><span>flow secret <span style="color:#d699b6">set</span> api-key <span style="color:#b2c98f">&#34;prod-456&#34;</span>
</span></span></code></pre></div><p>This allows for clean secret references in executables: <code>secretRef: &quot;api-key&quot;</code> uses current vault, <code>secretRef: &quot;production/api-key&quot;</code> is explicit.</p>
<h2 id="technical-decisions">Technical decisions</h2>
<h3 id="executable-composition">Executable composition</h3>
<p>Executables aren&rsquo;t just scripts - they&rsquo;re composable units with conditional logic:</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#7a8478">serial</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#7a8478">failFast</span>: <span style="color:#e67e80">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#7a8478">execs</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#7a8478">if</span>: os == &#34;darwin&#34;
</span></span><span style="display:flex;"><span>      <span style="color:#7a8478">cmd</span>: <span style="color:#b2c98f">&#34;command -v mytool || brew install mytool&#34;</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#7a8478">if</span>: env[&#34;PUSH&#34;] == &#34;true&#34;
</span></span><span style="display:flex;"><span>      <span style="color:#7a8478">cmd</span>: make image
</span></span><span style="display:flex;"><span>    - <span style="color:#7a8478">ref</span>: deploy development
</span></span><span style="display:flex;"><span>      <span style="color:#7a8478">reviewRequired</span>: <span style="color:#e67e80">true</span>  <span style="color:#859289;font-style:italic"># Pauses for human confirmation</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#7a8478">ref</span>: launch app
</span></span></code></pre></div><p>The expression language (using <a href="https://github.com/expr-lang/expr">Expr</a>) has access to OS info, environment variables, and flow&rsquo;s cache. It&rsquo;s like having bash conditionals but declarative.</p>
<h3 id="process-architecture">Process architecture</h3>
<p>The desktop app&rsquo;s process model is pretty simple. Each user action spawns a CLI process:</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#e67e80">#[tauri::command]</span>
</span></span><span style="display:flex;"><span><span style="color:#e67e80">async</span> <span style="color:#e67e80">fn</span> <span style="color:#b2c98f">get_workspaces</span>() -&gt; <span style="color:#d699b6">Result</span><span style="color:#7a8478">&lt;</span><span style="color:#d699b6">Vec</span><span style="color:#7a8478">&lt;</span>Workspace<span style="color:#7a8478">&gt;</span>, <span style="color:#d699b6">String</span><span style="color:#7a8478">&gt;</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#e67e80">let</span> output <span style="color:#7a8478">=</span> Command::new(<span style="color:#b2c98f">&#34;flow&#34;</span>)
</span></span><span style="display:flex;"><span>        .args([<span style="color:#b2c98f">&#34;workspace&#34;</span>, <span style="color:#b2c98f">&#34;list&#34;</span>, <span style="color:#b2c98f">&#34;--output&#34;</span>, <span style="color:#b2c98f">&#34;json&#34;</span>])
</span></span><span style="display:flex;"><span>        .output().<span style="color:#e67e80">await</span><span style="color:#7a8478">?</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    serde_json::from_slice(<span style="color:#7a8478">&amp;</span>output.stdout)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This seems inefficient but has huge benefits:</p>
<ul>
<li>Desktop crashes don&rsquo;t corrupt CLI state</li>
<li>CLI updates immediately benefit desktop</li>
<li>No state synchronization between processes</li>
<li>Easy to debug - each operation is a discrete CLI command</li>
</ul>
<h2 id="rc-moments-that-shaped-the-code">RC moments that shaped the code</h2>
<p><strong>Pair Programming</strong>: I paired on setting up Tauri and trying to integrate it with the CLI. We came up with the great idea of using some of my existing CLI output formatting options to get data through the app. This turned out to be a great decision to make on the fly. Conversations with others helped me confirm that CLI-as-source-of-truth wasn&rsquo;t a compromise - it was the right abstraction.</p>
<p><strong>The Feedback Loop</strong>: RC&rsquo;s culture of sharing work-in-progress meant getting feedback on half-baked ideas. I&rsquo;ve enjoyed presenting and demoing my progress throughout my time. I&rsquo;d thought extensions would be a neat feature but I never actually needed them myself. Hearing about the different ways that others think flow could be extended convinced me to reopen an <a href="https://github.com/jahvon/flow/issues/185">issue</a> I closed.</p>
<p><strong>Community Inspiration</strong>: Seeing the variety of projects and approaches at RC has reinforced my belief that developer tools should be adaptable rather than prescriptive. Everyone has their own workflow, and the best tools are the ones that bend to fit how you think, not the other way around.</p>
<h2 id="next-up">Next up</h2>
<h3 id="flow-mcp-server">flow MCP server</h3>
<p>I&rsquo;ve started using Claude Code and this has inspired me to learn how to create a Model Context Protocol server that will allow AI to understand flow workspaces and executables natively. I&rsquo;d love to eventually have something that enables:</p>
<ul>
<li>Browsing flow files and suggesting syntax improvements</li>
<li>Generating new workflows from natural language</li>
<li>Debugging failures with full workspace context 🚀</li>
</ul>
<h3 id="wasm-plugin-system">WASM plugin system</h3>
<p>Extending flow with WASM-integrated extensions. I want to try to allow automations to be programmable in two ways:</p>
<ul>
<li><strong>Executable template generator</strong>: Plugins that run template generation for flow-discoverable executables (from APIs, templates, external sources). I&rsquo;m thinking of something like Taskfile/just → flow executable integrations to start</li>
<li><strong>WASM Runtime executable type</strong>: Plugin executables that run sandboxed through flow</li>
</ul>
<p>This will be my first time getting hands-on with WebAssembly and I already have many ideas for cool plugins that will allow me to tinker with a variety of languages in the future.</p>
<h3 id="test--release-improvements">Test &amp; release improvements</h3>
<p>The best way to try out some of these new and upcoming features would be to clone the flow repo and run the <code>build binary</code> executable to get a local go build. Note that the main branch is not guaranteed to be stable, though.</p>
<p>With a new component in the flow ecosystem, I need to level up the test and release process as I prepare for v1 over the next couple of months. This means learning frontend testing practices, updating my GitHub workflows to handle multi-language builds, and rethinking the installation process to bundle the desktop app alongside the CLI.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Distributing Work with Go Concurrency</title>
      <link>https://jahvon.dev/notes/distributing-work/</link>
      <pubDate>Tue, 27 May 2025 00:00:00 +0000</pubDate>
      <guid>https://jahvon.dev/notes/distributing-work/</guid>
      <description>&lt;p&gt;A few months back, I worked through VictoriaMetrics&amp;rsquo; &lt;a href=&#34;https://victoriametrics.com/blog/go-sync-mutex/index.html&#34;&gt;Go concurrency series&lt;/a&gt; and wanted to get some practice. So I implemented a few distributed systems, work distribution patterns to see how the concurrency patterns translate.&lt;/p&gt;
&lt;p&gt;Work distribution is fundamental to building scalable systems - you need ways to spread processing across multiple components while coordinating the results. Go&amp;rsquo;s goroutines and channels map well to distributed system concepts - channels as service communication, goroutines as system components, WaitGroups for coordination. Here&amp;rsquo;s what I learned.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>A few months back, I worked through VictoriaMetrics&rsquo; <a href="https://victoriametrics.com/blog/go-sync-mutex/index.html">Go concurrency series</a> and wanted to get some practice. So I implemented a few distributed systems, work distribution patterns to see how the concurrency patterns translate.</p>
<p>Work distribution is fundamental to building scalable systems - you need ways to spread processing across multiple components while coordinating the results. Go&rsquo;s goroutines and channels map well to distributed system concepts - channels as service communication, goroutines as system components, WaitGroups for coordination. Here&rsquo;s what I learned.</p>
<h2 id="producer-consumer-async-work-distribution">Producer-Consumer: Async Work Distribution</h2>
<p>Producers generate work and send it through channels while consumers process it asynchronously.</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e67e80">type</span> ConsumerResult <span style="color:#e67e80">struct</span> {
</span></span><span style="display:flex;"><span>    ConsumerID <span style="color:#dbbc7f">int</span>
</span></span><span style="display:flex;"><span>    Data       <span style="color:#dbbc7f">string</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#859289;font-style:italic">// start multiple consumers</span>
</span></span><span style="display:flex;"><span><span style="color:#e67e80">for</span> id <span style="color:#7a8478">:=</span> <span style="color:#d699b6">0</span>; id &lt; numConsumers; id<span style="color:#7a8478">++</span> {
</span></span><span style="display:flex;"><span>    wg.<span style="color:#b2c98f">Add</span>(<span style="color:#d699b6">1</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#e67e80">go</span> <span style="color:#e67e80">func</span>(consumerID <span style="color:#dbbc7f">int</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#e67e80">defer</span> wg.<span style="color:#b2c98f">Done</span>()
</span></span><span style="display:flex;"><span>        <span style="color:#e67e80">for</span> msg <span style="color:#7a8478">:=</span> <span style="color:#e67e80">range</span> msgChan {
</span></span><span style="display:flex;"><span>            result <span style="color:#7a8478">:=</span> ConsumerResult{
</span></span><span style="display:flex;"><span>                ConsumerID: consumerID,
</span></span><span style="display:flex;"><span>                Data: fmt.<span style="color:#b2c98f">Sprintf</span>(<span style="color:#b2c98f">&#34;processed-%d&#34;</span>, msg),
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>            resultChan <span style="color:#7a8478">&lt;-</span> result
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }(id)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#859289;font-style:italic">// start a single producer that sends work into a channel</span>
</span></span><span style="display:flex;"><span><span style="color:#e67e80">go</span> <span style="color:#e67e80">func</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#e67e80">defer</span> <span style="color:#d699b6">close</span>(msgChan)
</span></span><span style="display:flex;"><span>    <span style="color:#e67e80">for</span> i <span style="color:#7a8478">:=</span> <span style="color:#d699b6">1</span>; i <span style="color:#7a8478">&lt;=</span> <span style="color:#d699b6">25</span>; i<span style="color:#7a8478">++</span> {
</span></span><span style="display:flex;"><span>        msgChan <span style="color:#7a8478">&lt;-</span> i
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}()
</span></span></code></pre></div><p>Buffered channels give you throttling - if consumers can&rsquo;t keep up, the producer blocks instead of consuming memory.</p>
<p>Use this pattern for event streaming, async processing, or decoupling generation speed from processing speed. It maps directly to Kafka or microservice event handling.</p>
<h2 id="worker-pools-controlled-work-distribution">Worker Pools: Controlled Work Distribution</h2>
<p>Worker pools give you structure - fixed number of workers pulling from the same job queue. It&rsquo;s like running N service instances behind a load balancer.</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e67e80">type</span> PoolJob <span style="color:#e67e80">struct</span> {
</span></span><span style="display:flex;"><span>    ID   <span style="color:#dbbc7f">int</span>
</span></span><span style="display:flex;"><span>    Data <span style="color:#dbbc7f">string</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#859289;font-style:italic">// start a fixed number of workers</span>
</span></span><span style="display:flex;"><span><span style="color:#e67e80">for</span> i <span style="color:#7a8478">:=</span> <span style="color:#d699b6">0</span>; i &lt; numWorkers; i<span style="color:#7a8478">++</span> {
</span></span><span style="display:flex;"><span>    wg.<span style="color:#b2c98f">Add</span>(<span style="color:#d699b6">1</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#e67e80">go</span> <span style="color:#e67e80">func</span>(workerID <span style="color:#dbbc7f">int</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#e67e80">defer</span> wg.<span style="color:#b2c98f">Done</span>()
</span></span><span style="display:flex;"><span>        <span style="color:#e67e80">for</span> job <span style="color:#7a8478">:=</span> <span style="color:#e67e80">range</span> jobChan {
</span></span><span style="display:flex;"><span>            <span style="color:#859289;font-style:italic">// do some work</span>
</span></span><span style="display:flex;"><span>            time.<span style="color:#b2c98f">Sleep</span>(<span style="color:#d699b6">100</span> <span style="color:#7a8478">*</span> time.Millisecond)
</span></span><span style="display:flex;"><span>            results <span style="color:#7a8478">&lt;-</span> PoolResult{
</span></span><span style="display:flex;"><span>                WorkerID: workerID,
</span></span><span style="display:flex;"><span>                JobID:    job.ID,
</span></span><span style="display:flex;"><span>                Value:    fmt.<span style="color:#b2c98f">Sprintf</span>(<span style="color:#b2c98f">&#34;processed-%s&#34;</span>, job.Data),
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }(i)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The job channel acts like a load balancer - work goes to whichever worker is available.</p>
<p>This pattern is good for CPU-heavy tasks or when you need predictable resource usage. It&rsquo;s similar to scaling microservice instances for ingress traffic.</p>
<h2 id="batch-processing-efficient-work-distribution">Batch Processing: Efficient Work Distribution</h2>
<p>Sometimes you need to group items into batches for efficiency or to respect downstream rate limits. This example handles batching by size and by time.</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e67e80">func</span> (p <span style="color:#7a8478">*</span>BatchProcessor) <span style="color:#b2c98f">startBatchAggregator</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#e67e80">go</span> <span style="color:#e67e80">func</span>() {
</span></span><span style="display:flex;"><span>        batch <span style="color:#7a8478">:=</span> <span style="color:#d699b6">make</span>([]<span style="color:#dbbc7f">int</span>, <span style="color:#d699b6">0</span>, p.batchSize)
</span></span><span style="display:flex;"><span>        flushTimer <span style="color:#7a8478">:=</span> time.<span style="color:#b2c98f">NewTimer</span>(<span style="color:#d699b6">2</span> <span style="color:#7a8478">*</span> time.Second)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        sendBatch <span style="color:#7a8478">:=</span> <span style="color:#e67e80">func</span>() {
</span></span><span style="display:flex;"><span>            <span style="color:#7a8478">&lt;-</span>p.rateLimiter.C <span style="color:#859289;font-style:italic">// wait for rate limiter</span>
</span></span><span style="display:flex;"><span>            batchCopy <span style="color:#7a8478">:=</span> <span style="color:#d699b6">make</span>([]<span style="color:#dbbc7f">int</span>, <span style="color:#d699b6">len</span>(batch))
</span></span><span style="display:flex;"><span>            <span style="color:#d699b6">copy</span>(batchCopy, batch)
</span></span><span style="display:flex;"><span>            p.batchChan <span style="color:#7a8478">&lt;-</span> batchCopy
</span></span><span style="display:flex;"><span>            batch = batch[:<span style="color:#d699b6">0</span>]
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#e67e80">for</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#e67e80">select</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#e67e80">case</span> item, ok <span style="color:#7a8478">:=</span> <span style="color:#7a8478">&lt;-</span>p.itemChan:
</span></span><span style="display:flex;"><span>                <span style="color:#e67e80">if</span> !ok {
</span></span><span style="display:flex;"><span>                    <span style="color:#e67e80">if</span> <span style="color:#d699b6">len</span>(batch) &gt; <span style="color:#d699b6">0</span> {
</span></span><span style="display:flex;"><span>                        <span style="color:#b2c98f">sendBatch</span>()
</span></span><span style="display:flex;"><span>                    }
</span></span><span style="display:flex;"><span>                    <span style="color:#d699b6">close</span>(p.batchChan)
</span></span><span style="display:flex;"><span>                    <span style="color:#e67e80">return</span>
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                batch = <span style="color:#d699b6">append</span>(batch, item)
</span></span><span style="display:flex;"><span>                <span style="color:#e67e80">if</span> <span style="color:#d699b6">len</span>(batch) <span style="color:#7a8478">&gt;=</span> p.batchSize {
</span></span><span style="display:flex;"><span>                    <span style="color:#b2c98f">sendBatch</span>()
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#e67e80">case</span> <span style="color:#7a8478">&lt;-</span>flushTimer.C:
</span></span><span style="display:flex;"><span>                <span style="color:#e67e80">if</span> <span style="color:#d699b6">len</span>(batch) &gt; <span style="color:#d699b6">0</span> {
</span></span><span style="display:flex;"><span>                    <span style="color:#b2c98f">sendBatch</span>()
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }()
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The <code>select</code> with the flush timer gives you batches when they&rsquo;re full OR when time runs out. The rate limiter prevents overwhelming the batch processor and its external dependencies. I used a simple timer here, but you can replace it with much more sophisticated limiting logic as needed.</p>
<p>The batch processing pattern works well for database bulk operations, API integrations with rate limits, or protecting downstream services.</p>
<h2 id="a-few-notes">A Few Notes</h2>
<p>Working through these patterns reinforced a few things:</p>
<ul>
<li>Channels behave like message queues with capacity limits and natural flow control.</li>
<li>Multiple goroutines running the same function is basically horizontal scaling - same patterns you&rsquo;d use for scaling system components.</li>
<li>These patterns compose well. Producer-consumer provides the foundation, worker pools add structure, batching adds efficiency.</li>
</ul>
<table>
  <thead>
      <tr>
          <th>Pattern</th>
          <th>Analogy</th>
          <th>Use Case</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Producer-Consumer</td>
          <td>Message queues, event streams</td>
          <td>Event-driven architectures, async processing</td>
      </tr>
      <tr>
          <td>Worker Pools</td>
          <td>Load-balanced system components</td>
          <td>Controlled concurrency, predictable resources</td>
      </tr>
      <tr>
          <td>Batch Processing</td>
          <td>ETL pipelines, bulk APIs</td>
          <td>Rate limiting, bulk operations</td>
      </tr>
  </tbody>
</table>
<h3 id="error-handling">Error Handling</h3>
<p>Error handling in concurrent code needs to be explicit and planned upfront, similar to how distributed systems need circuit breakers and retry logic. I used result structs that carry either data or errors:</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e67e80">type</span> WorkResult <span style="color:#e67e80">struct</span> {
</span></span><span style="display:flex;"><span>    Data <span style="color:#dbbc7f">string</span>
</span></span><span style="display:flex;"><span>    Err  <span style="color:#dbbc7f">error</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#e67e80">func</span> <span style="color:#b2c98f">worker</span>(jobs <span style="color:#7a8478">&lt;-</span><span style="color:#e67e80">chan</span> <span style="color:#dbbc7f">int</span>, results <span style="color:#e67e80">chan</span><span style="color:#7a8478">&lt;-</span> WorkResult) {
</span></span><span style="display:flex;"><span>    <span style="color:#e67e80">for</span> job <span style="color:#7a8478">:=</span> <span style="color:#e67e80">range</span> jobs {
</span></span><span style="display:flex;"><span>        <span style="color:#e67e80">if</span> job<span style="color:#7a8478">%</span><span style="color:#d699b6">7</span> <span style="color:#7a8478">==</span> <span style="color:#d699b6">0</span> { <span style="color:#859289;font-style:italic">// simulate some failures</span>
</span></span><span style="display:flex;"><span>            results <span style="color:#7a8478">&lt;-</span> WorkResult{Err: fmt.<span style="color:#b2c98f">Errorf</span>(<span style="color:#b2c98f">&#34;job %d failed&#34;</span>, job)}
</span></span><span style="display:flex;"><span>            <span style="color:#e67e80">continue</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        results <span style="color:#7a8478">&lt;-</span> WorkResult{Data: fmt.<span style="color:#b2c98f">Sprintf</span>(<span style="color:#b2c98f">&#34;processed-%d&#34;</span>, job)}
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>For timeouts and cancellation, <code>context.Context</code> works well:</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e67e80">func</span> <span style="color:#b2c98f">workerWithTimeout</span>(ctx context.Context, jobs <span style="color:#7a8478">&lt;-</span><span style="color:#e67e80">chan</span> <span style="color:#dbbc7f">int</span>, results <span style="color:#e67e80">chan</span><span style="color:#7a8478">&lt;-</span> WorkResult) {
</span></span><span style="display:flex;"><span>    <span style="color:#e67e80">for</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#e67e80">select</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#e67e80">case</span> job <span style="color:#7a8478">:=</span> <span style="color:#7a8478">&lt;-</span>jobs:
</span></span><span style="display:flex;"><span>            <span style="color:#859289;font-style:italic">// process the job</span>
</span></span><span style="display:flex;"><span>        <span style="color:#e67e80">case</span> <span style="color:#7a8478">&lt;-</span>ctx.<span style="color:#b2c98f">Done</span>():
</span></span><span style="display:flex;"><span>            results <span style="color:#7a8478">&lt;-</span> WorkResult{Err: ctx.<span style="color:#b2c98f">Err</span>()}
</span></span><span style="display:flex;"><span>            <span style="color:#e67e80">return</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="beyond-the-basics">Beyond the basics</h3>
<p>These patterns scratch the surface of Go&rsquo;s concurrency toolkit. The VictoriaMetrics series I mentioned dives deep into more advanced primitives like <code>sync.Mutex</code> for protecting shared state, <code>sync.Pool</code> for object reuse, <code>sync.Once</code> for one-time initialization, and <code>sync.Map</code> for concurrent map access. I recommend checking it out if you haven&rsquo;t already!</p>
<p>I intentionally stuck to channels and WaitGroups in my examples here - they mirror message passing between services naturally and keep the code readable. Once those patterns are solid, adding mutexes and other synchronization primitives becomes intuitive because you already understand the coordination challenges.</p>
<p>As you build more complex systems, you&rsquo;ll need these other tools. Mutexes become your distributed locks, sync.Pool mirrors connection pooling in microservices, sync.Once handles singleton initialization across service instances (similar to leader election), and sync.Map acts like shared caches that multiple services access concurrently.</p>
<p><strong><a href="https://github.com/jahvon/go-concurrency-examples">Full code examples on GitHub</a></strong></p>
<p><em>Next: circuit breaker and fan-in/fan-out implementations.</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>Forging flow: My Journey of Creation and Learning</title>
      <link>https://jahvon.dev/notes/forging-flow/</link>
      <pubDate>Sat, 08 Feb 2025 00:00:00 +0000</pubDate>
      <guid>https://jahvon.dev/notes/forging-flow/</guid>
      <description>&lt;p&gt;Over the last couple of years, I have been having fun experimenting with ways to streamline my developer experience. This may largely stem from my Developer Experience (DevX) focus as a software / platform engineer in the CarGurus DevX organization. (&lt;em&gt;side note: Check out &lt;a href=&#34;https://www.cargurus.dev/How-CarGurus-is-supercharging-our-microservice-developer-experience/&#34;&gt;this blog post&lt;/a&gt; I wrote on how we supercharged the experience for our Product Engineers&lt;/em&gt;)&lt;/p&gt;
&lt;p&gt;However, the challenges that &lt;em&gt;I&lt;/em&gt; face at work and on my side projects aren&amp;rsquo;t quite the same as the ones faced by CarGurus product engineers. I started to find myself drowning in a sea of scattered commands, scripts, and tools. This, combined with my desire to find opportunities to learn more about Go patterns and libraries in a low-risk way, motivated me to invest some free time developing an &amp;ldquo;integrated development platform&amp;rdquo; - or at least the foundations for one!&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Over the last couple of years, I have been having fun experimenting with ways to streamline my developer experience. This may largely stem from my Developer Experience (DevX) focus as a software / platform engineer in the CarGurus DevX organization. (<em>side note: Check out <a href="https://www.cargurus.dev/How-CarGurus-is-supercharging-our-microservice-developer-experience/">this blog post</a> I wrote on how we supercharged the experience for our Product Engineers</em>)</p>
<p>However, the challenges that <em>I</em> face at work and on my side projects aren&rsquo;t quite the same as the ones faced by CarGurus product engineers. I started to find myself drowning in a sea of scattered commands, scripts, and tools. This, combined with my desire to find opportunities to learn more about Go patterns and libraries in a low-risk way, motivated me to invest some free time developing an &ldquo;integrated development platform&rdquo; - or at least the foundations for one!</p>
<p>Enter flow: my open-source task runner and workflow automation tool. What started as a simple itch to scratch has evolved into the foundations of a comprehensive platform for wrangling dev workflows across projects. Looking back, I realize it would have been easier to just migrate everything into a tool like <a href="https://taskfile.dev/">Taskfile</a> or <a href="https://just.systems/">Just</a>, but my vision for my own personal platform doesn&rsquo;t stop at the CLI. Taking that route also would have left my learning desires unmet. While much of my influence for flow comes from cloud-native projects and ideals, I&rsquo;ve approached it from a local-first perspective - one where repeatable &ldquo;micro-workflows&rdquo; can be pieced together however <em>you</em> desire; making them easily discoverable, automated, and observable.</p>
<p>It&rsquo;s ambitious, but that&rsquo;s what excites me! There&rsquo;s so much experimenting and learning ahead. This is my first blog post, but if this interests you, please return! I&rsquo;ll be using it to document my learnings and progress on this project and some of my other side projects.</p>
<h2 id="building-the-foundation">Building the Foundation</h2>
<p>My first build of flow centered around two simple concepts driven by YAML files:</p>
<ul>
<li>Workspaces for organizing tasks across projects/repos</li>
<li>Executables for defining those tasks</li>
</ul>
<p>I included a simple <a href="https://github.com/rivo/tview/">tview</a> terminal UI implementation to simplify the discovery of workspaces and executables across my system. While I&rsquo;ve since moved away from that library, it helped me conceptualize much of the current TUI.</p>
<p>Through usage, I found myself iterating <em>a lot</em>. As I onboarded more workspaces and as those workspaces grew in complexity, flow&rsquo;s feature set had to grow. My favorite components to implement have been the <a href="https://github.com/charmbracelet/bubbletea">bubbletea</a> TUI framework, an internal documentation generator for the <a href="https://flowexec.io/">flowexec.io</a> site, the <a href="https://flowexec.io/#/guide/templating">templating</a> workflow, and the <a href="https://flowexec.io/#/guide/state">state</a> and <a href="https://flowexec.io/#/guide/conditional">conditional</a> management of  serial and parallel executable types. &rsquo;ll dive deeper into those in follow-up posts, but I invite you to explore the guides at flowexec.io for a complete overview of where flow stands today.</p>
<p>At it&rsquo;s core, the flow CLI is a YAML-driven task runner. Here is an example of a flow file that I have for my Authentik server deployed in my home cluster; it uses the <code>exec</code> executable type to define the command that&rsquo;s run:</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#7a8478">namespace</span>: authentik <span style="color:#859289;font-style:italic"># optional, additional grouping in a workspace</span>
</span></span><span style="display:flex;"><span><span style="color:#7a8478">tags</span>: [k8s, auth] <span style="color:#859289;font-style:italic"># useful for filtering the `flow library` command</span>
</span></span><span style="display:flex;"><span><span style="color:#859289;font-style:italic"># this description is rendered as markdown (alongside other executable info)</span>
</span></span><span style="display:flex;"><span><span style="color:#859289;font-style:italic"># when viewing in the `flow library`.</span>
</span></span><span style="display:flex;"><span><span style="color:#7a8478">description</span>: |<span style="color:#b2c98f">
</span></span></span><span style="display:flex;"><span><span style="color:#b2c98f">  **References:**
</span></span></span><span style="display:flex;"><span><span style="color:#b2c98f">  - https://goauthentik.io/
</span></span></span><span style="display:flex;"><span><span style="color:#b2c98f">  - https://github.com/goauthentik/helm</span>
</span></span><span style="display:flex;"><span><span style="color:#7a8478">executables</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#859289;font-style:italic"># flow install authentik:app</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#7a8478">verb</span>: install
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">name</span>: app
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">aliases</span>: [chart]
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">description</span>: Upgrade/install Authentik Helm chart
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">exec</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#7a8478">params</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#859289;font-style:italic"># secrets are managed with the integrated vault via the `flow secret` command</span>
</span></span><span style="display:flex;"><span>        - <span style="color:#7a8478">secretRef</span>: authentik-secret-key
</span></span><span style="display:flex;"><span>          <span style="color:#7a8478">envKey</span>: AUTHENTIK_SECRET_KEY
</span></span><span style="display:flex;"><span>        - <span style="color:#7a8478">secretRef</span>: authentik-db-password
</span></span><span style="display:flex;"><span>          <span style="color:#7a8478">envKey</span>: AUTHENTIK_DB_PASSWORD
</span></span><span style="display:flex;"><span>      <span style="color:#7a8478">cmd</span>: |<span style="color:#b2c98f">
</span></span></span><span style="display:flex;"><span><span style="color:#b2c98f">        helm upgrade --install authentik authentik/authentik \
</span></span></span><span style="display:flex;"><span><span style="color:#b2c98f">          --version 2024.10.4 \
</span></span></span><span style="display:flex;"><span><span style="color:#b2c98f">          --namespace auth --create-namespace \
</span></span></span><span style="display:flex;"><span><span style="color:#b2c98f">          --set authentik.secret_key=$AUTHENTIK_SECRET_KEY \
</span></span></span><span style="display:flex;"><span><span style="color:#b2c98f">          --set authentik.postgresql.password=$AUTHENTIK_DB_PASSWORD \
</span></span></span><span style="display:flex;"><span><span style="color:#b2c98f">          --set postgresql.auth.password=$AUTHENTIK_DB_PASSWORD \
</span></span></span><span style="display:flex;"><span><span style="color:#b2c98f">          --set postgresql.postgresqlPassword=$AUTHENTIK_DB_PASSWORD \
</span></span></span><span style="display:flex;"><span><span style="color:#b2c98f">          -f values.yaml</span>
</span></span></code></pre></div><p>The flow CLI provides a consistent experience for all executable runs and searches. This includes automatically generating a summary markdown document viewable with the <code>flow library</code> command, log formatting and archiving, and a configurable TUI experience.</p>
<p>This will show up in the <code>flow library</code> as rendered markdown:</p>
<p><img alt="Authentik Executable" loading="lazy" src="/images/library-authentik-exec.png"></p>
<p>As my needs evolved, I added more executable configurations and types. Here&rsquo;s an example of a common <code>request</code> executable I use to pause my home&rsquo;s pi.hole blocking:</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#7a8478">executables</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#859289;font-style:italic"># flow pause pihole</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#7a8478">verb</span>: pause
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">name</span>: pihole
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">request</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#7a8478">method</span>: <span style="color:#b2c98f">&#34;POST&#34;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#7a8478">args</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#7a8478">pos</span>: <span style="color:#d699b6">1</span>
</span></span><span style="display:flex;"><span>          <span style="color:#7a8478">envKey</span>: DURATION
</span></span><span style="display:flex;"><span>          <span style="color:#7a8478">default</span>: <span style="color:#d699b6">300</span>
</span></span><span style="display:flex;"><span>          <span style="color:#7a8478">type</span>: int
</span></span><span style="display:flex;"><span>      <span style="color:#7a8478">params</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#7a8478">secretRef</span>: pihole-pwhash
</span></span><span style="display:flex;"><span>          <span style="color:#7a8478">envKey</span>: PWHASH
</span></span><span style="display:flex;"><span>      <span style="color:#7a8478">url</span>: http://pi.hole/admin/api.php?disable=$DURATION&amp;auth=$PWHASH
</span></span><span style="display:flex;"><span>      <span style="color:#7a8478">validStatusCodes</span>: [<span style="color:#d699b6">200</span>]
</span></span><span style="display:flex;"><span>      <span style="color:#7a8478">logResponse</span>: <span style="color:#e67e80">true</span>
</span></span><span style="display:flex;"><span>      <span style="color:#7a8478">transformResponse</span>: if .status == &#34;disabled&#34; then .status = &#34;paused&#34; else . end
</span></span></code></pre></div><p><img alt="PiHole Executable" loading="lazy" src="/images/library-pihole-exec.png"></p>
<p>Here&rsquo;s an example of the log output:</p>
<p><img alt="PiHole Executable Logs" loading="lazy" src="/images/log-pihole-exec.png"></p>
<h2 id="lessons-learned">Lessons Learned</h2>
<p>Building flow has been an incredible opportunity to deepen my understanding of Go and its ecosystem. Here are a few key lessons that have significantly shaped how I write Go now:</p>
<h3 id="small-packages-and-interfaces-made-testing-a-breeze">Small Packages and Interfaces Made Testing a Breeze</h3>
<p>This approach makes testing easier, improves code organization, and makes refactoring as ideas evolve a joy.</p>
<p>Each executable type has its own Runner, making it simple to extend the system with new types. Here&rsquo;s a snippet that demonstrates the ease of using this type when assigning an executable to a <code>Runner</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#859289;font-style:italic">//go:generate mockgen -destination=mocks/mock_runner.go -package=mocks github.com/jahvon/flow/internal/runner Runner</span>
</span></span><span style="display:flex;"><span><span style="color:#e67e80">type</span> Runner <span style="color:#e67e80">interface</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#b2c98f">Name</span>() <span style="color:#dbbc7f">string</span>
</span></span><span style="display:flex;"><span>    <span style="color:#b2c98f">Exec</span>(ctx <span style="color:#7a8478">*</span>context.Context, e <span style="color:#7a8478">*</span>executable.Executable, eng engine.Engine, inputEnv <span style="color:#e67e80">map</span>[<span style="color:#dbbc7f">string</span>]<span style="color:#dbbc7f">string</span>) <span style="color:#dbbc7f">error</span>
</span></span><span style="display:flex;"><span>    <span style="color:#b2c98f">IsCompatible</span>(executable <span style="color:#7a8478">*</span>executable.Executable) <span style="color:#dbbc7f">bool</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This interface allows me to easily add new executable types by just implementing these three methods. The core execution logic remains clean and extensible:</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e67e80">func</span> <span style="color:#b2c98f">Exec</span>(
</span></span><span style="display:flex;"><span>    ctx <span style="color:#7a8478">*</span>context.Context,
</span></span><span style="display:flex;"><span>    executable <span style="color:#7a8478">*</span>executable.Executable,
</span></span><span style="display:flex;"><span>    eng engine.Engine,
</span></span><span style="display:flex;"><span>    inputEnv <span style="color:#e67e80">map</span>[<span style="color:#dbbc7f">string</span>]<span style="color:#dbbc7f">string</span>,
</span></span><span style="display:flex;"><span>) <span style="color:#dbbc7f">error</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#e67e80">var</span> assignedRunner Runner
</span></span><span style="display:flex;"><span>    <span style="color:#e67e80">for</span> _, runner <span style="color:#7a8478">:=</span> <span style="color:#e67e80">range</span> registeredRunners {
</span></span><span style="display:flex;"><span>       <span style="color:#e67e80">if</span> runner.<span style="color:#b2c98f">IsCompatible</span>(executable) {
</span></span><span style="display:flex;"><span>          assignedRunner = runner
</span></span><span style="display:flex;"><span>          <span style="color:#e67e80">break</span>
</span></span><span style="display:flex;"><span>       }
</span></span><span style="display:flex;"><span>    }    <span style="color:#e67e80">if</span> assignedRunner <span style="color:#7a8478">==</span> <span style="color:#e67e80">nil</span> {
</span></span><span style="display:flex;"><span>       <span style="color:#e67e80">return</span> fmt.<span style="color:#b2c98f">Errorf</span>(<span style="color:#b2c98f">&#34;compatible runner not found for executable %s&#34;</span>, executable.<span style="color:#b2c98f">ID</span>())
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#e67e80">if</span> executable.Timeout <span style="color:#7a8478">==</span> <span style="color:#d699b6">0</span> {
</span></span><span style="display:flex;"><span>       <span style="color:#e67e80">return</span> assignedRunner.<span style="color:#b2c98f">Exec</span>(ctx, executable, eng, inputEnv)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    done <span style="color:#7a8478">:=</span> <span style="color:#d699b6">make</span>(<span style="color:#e67e80">chan</span> <span style="color:#dbbc7f">error</span>, <span style="color:#d699b6">1</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#e67e80">go</span> <span style="color:#e67e80">func</span>() {
</span></span><span style="display:flex;"><span>       done <span style="color:#7a8478">&lt;-</span> assignedRunner.<span style="color:#b2c98f">Exec</span>(ctx, executable, eng, inputEnv)
</span></span><span style="display:flex;"><span>    }()
</span></span><span style="display:flex;"><span>    <span style="color:#e67e80">select</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#e67e80">case</span> err <span style="color:#7a8478">:=</span> <span style="color:#7a8478">&lt;-</span>done:
</span></span><span style="display:flex;"><span>       <span style="color:#e67e80">return</span> err
</span></span><span style="display:flex;"><span>    <span style="color:#e67e80">case</span> <span style="color:#7a8478">&lt;-</span>time.<span style="color:#b2c98f">After</span>(executable.Timeout):
</span></span><span style="display:flex;"><span>       <span style="color:#e67e80">return</span> fmt.<span style="color:#b2c98f">Errorf</span>(<span style="color:#b2c98f">&#34;timeout after %v&#34;</span>, executable.Timeout)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>For testing, I use <a href="https://onsi.github.io/ginkgo/">ginkgo</a> for its expressive BDD-style syntax and <a href="https://github.com/uber-go/mock">GoMock</a> to generate a mock runner. This mock simulates serial and parallel execution without the complexity of managing real subprocesses or network calls. This approach has been invaluable for verifying complex concurrent behaviors, especially when testing features like timeout handling, parallel execution limits, and failure modes in a reliable, repeatable way.</p>
<h3 id="build-better-abstractions-with-service-layers">Build Better Abstractions with Service Layers</h3>
<p>When working with third-party modules or I/O components, wrapping your interaction with a service layer is invaluable. It keeps business logic decoupled from implementation details and simplifies testing and refactoring. In flow, I use this pattern extensively for components like shell operations, file system operations, and process management.</p>
<p>For example, my run service abstracts away the complexities of running shell operations with the <a href="https://github.com/mvdan/sh">github.com/mvdan/sh</a> library.  This means if I need to change how shell commands are executed or add new shell features, I only need to update the service implementation, not the core application logic. You can explore some of my service implementations in the <a href="https://github.com/jahvon/flow/tree/main/internal/services">source code</a>.</p>
<h3 id="good-tools-are-worth-the-investment">Good Tools Are Worth the Investment</h3>
<p>Investing in custom tooling or incoproating open source, especially for patterns like code generation, can significantly streamline your development workflow and reduce boilerplate. In flow, I define all types in YAML and use <a href="https://github.com/atombender/go-jsonschema">go-jsonschema</a> for <code>codegen</code>.</p>
<p>Here&rsquo;s an example of how my <code>Launch</code> executable type is defined:</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#7a8478">LaunchExecutableType</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#7a8478">type</span>: object
</span></span><span style="display:flex;"><span>  <span style="color:#7a8478">required</span>: [uri]
</span></span><span style="display:flex;"><span>  <span style="color:#7a8478">description</span>: Launches an application or opens a URI.
</span></span><span style="display:flex;"><span>  <span style="color:#7a8478">properties</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">params</span>:
</span></span><span style="display:flex;"><span>	  <span style="color:#7a8478">$ref</span>: <span style="color:#b2c98f">&#39;#/definitions/ParameterList&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">args</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#7a8478">$ref</span>: <span style="color:#b2c98f">&#39;#/definitions/ArgumentList&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">app</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#7a8478">type</span>: string
</span></span><span style="display:flex;"><span>      <span style="color:#7a8478">description</span>: The application to launch the URI with.
</span></span><span style="display:flex;"><span>      <span style="color:#7a8478">default</span>: <span style="color:#b2c98f">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">uri</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#7a8478">type</span>: string
</span></span><span style="display:flex;"><span>      <span style="color:#7a8478">description</span>: The URI to launch. This can be a file path or a web URL.
</span></span><span style="display:flex;"><span>      <span style="color:#7a8478">default</span>: <span style="color:#b2c98f">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">wait</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#7a8478">type</span>: boolean
</span></span><span style="display:flex;"><span>      <span style="color:#7a8478">description</span>: If set to true, the executable will wait for the launched application to exit before continuing.
</span></span><span style="display:flex;"><span>      <span style="color:#7a8478">default</span>: <span style="color:#e67e80">false</span>
</span></span></code></pre></div><p>This generates both the Go type and its documentation:</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#859289;font-style:italic">// Launches an application or opens a URI.</span>
</span></span><span style="display:flex;"><span><span style="color:#e67e80">type</span> LaunchExecutableType <span style="color:#e67e80">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#859289;font-style:italic">// The application to launch the URI with.</span>
</span></span><span style="display:flex;"><span>	App <span style="color:#dbbc7f">string</span> <span style="color:#b2c98f">`json:&#34;app,omitempty&#34; yaml:&#34;app,omitempty&#34; mapstructure:&#34;app,omitempty&#34;`</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#859289;font-style:italic">// Args corresponds to the JSON schema field &#34;args&#34;.</span>
</span></span><span style="display:flex;"><span>	Args ArgumentList <span style="color:#b2c98f">`json:&#34;args,omitempty&#34; yaml:&#34;args,omitempty&#34; mapstructure:&#34;args,omitempty&#34;`</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#859289;font-style:italic">// Params corresponds to the JSON schema field &#34;params&#34;.</span>
</span></span><span style="display:flex;"><span>	Params ParameterList <span style="color:#b2c98f">`json:&#34;params,omitempty&#34; yaml:&#34;params,omitempty&#34; mapstructure:&#34;params,omitempty&#34;`</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#859289;font-style:italic">// The URI to launch. This can be a file path or a web URL.</span>
</span></span><span style="display:flex;"><span>	URI <span style="color:#dbbc7f">string</span> <span style="color:#b2c98f">`json:&#34;uri&#34; yaml:&#34;uri&#34; mapstructure:&#34;uri&#34;`</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#859289;font-style:italic">// If set to true, the executable will wait for the launched application to exit</span>
</span></span><span style="display:flex;"><span>	<span style="color:#859289;font-style:italic">// before continuing.</span>
</span></span><span style="display:flex;"><span>	Wait <span style="color:#dbbc7f">bool</span> <span style="color:#b2c98f">`json:&#34;wait,omitempty&#34; yaml:&#34;wait,omitempty&#34; mapstructure:&#34;wait,omitempty&#34;`</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>I&rsquo;ve also built a <code>docsgen</code> tool that uses this same schema to generate structured documentation. This means my types, code, and documentation all stay in sync automatically. You can see the generated type documentation for Launch <a href="https://flowexec.io/#/types/flowfile?id=executablelaunchexecutabletype">here</a>.</p>
<p>This investment in tooling has paid off repeatedly, especially as flow&rsquo;s type system has grown more complex. It reduces errors, ensures consistency, and lets me focus on implementing features rather than maintaining boilerplate code.</p>
<h2 id="the-road-ahead">The Road Ahead</h2>
<p>Looking forward, I&rsquo;m excited to explore building extensions around the flow CLI, from allowing users to BYO-vault to providing a local browser-based UI for executing workflows and discovering what&rsquo;s on your machine.</p>
<p>flow is a reflection of my passion for crafting tools that make developers&rsquo; lives easier. What started as a personal project has grown into something I believe can help other developers take control of their development experience. I invite you to <a href="https://flowexec.io/#/development">contribute</a>, star the <a href="https://github.com/jahvon/flow">repo</a> to show your support, and to open issues to report bugs or suggest features!</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
