<?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>Flow on Jahvon Dockery</title>
    <link>https://jahvon.dev/tags/flow/</link>
    <description>Recent content in Flow 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/tags/flow/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>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>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>
    <item>
      <title>Flow</title>
      <link>https://jahvon.dev/architecture/flow/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://jahvon.dev/architecture/flow/</guid>
      <description>Flow is a local-first developer platform designed around composable YAML &amp;ldquo;executables&amp;rdquo;, built-in secret management, and cross-project automation.</description>
      <content:encoded><![CDATA[<div class="notice note" >
    <p class="notice-title">
        <span class="icon-notice baseline">
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 128 300 300">
  <path d="M150 128c82.813 0 150 67.188 150 150 0 82.813-67.188 150-150 150C67.187 428 0 360.812 0 278c0-82.813 67.188-150 150-150Zm25 243.555v-37.11c0-3.515-2.734-6.445-6.055-6.445h-37.5c-3.515 0-6.445 2.93-6.445 6.445v37.11c0 3.515 2.93 6.445 6.445 6.445h37.5c3.32 0 6.055-2.93 6.055-6.445Zm-.39-67.188 3.515-121.289c0-1.367-.586-2.734-1.953-3.516-1.172-.976-2.93-1.562-4.688-1.562h-42.968c-1.758 0-3.516.586-4.688 1.563-1.367.78-1.953 2.148-1.953 3.515l3.32 121.29c0 2.734 2.93 4.882 6.64 4.882h36.134c3.515 0 6.445-2.148 6.64-4.883Z"/>
</svg>

        </span>Note</p><p>This page covers the architectural decisions and design rationale behind <a href="https://flowexec.io">flow</a>. For a deep technical reference, see the <a href="https://deepwiki.com/flowexec/flow">architecture docs on DeepWiki</a>.</p>
<p><strong>Last updated</strong>: May 2026</p></div>

<p>Flow is a YAML-driven task runner and workflow automation tool designed around composable executables, built-in secret management, and cross-project automation. This document covers the core architectural decisions and how the pieces fit together.</p>
<h2 id="design-philosophy">Design Philosophy</h2>
<ul>
<li><strong>Developer-Centric</strong>: Flow should be designed with developers in mind, providing a powerful yet intuitive interface for managing automation tasks.</li>
<li><strong>Composable</strong>: Flow should allow users to compose complex workflows from simple, reusable executables, enabling cross-project automation and collaboration.</li>
<li><strong>Discoverable</strong>: Flow should make it easy to find and run executables across multiple projects and workspaces, with a focus on discoverability and usability.</li>
<li><strong><a href="https://www.inkandswitch.com/essay/local-first/">Local-first</a></strong>: Flow should be able to be run entirely on a local machine, with no external dependencies or cloud services required.</li>
<li><strong><a href="https://www.inkandswitch.com/malleable-software/">Malleable</a></strong>: Flow should be easily extensible and customizable, allowing users to adapt it to their specific needs and use it in a variety of contexts.</li>
</ul>
<h2 id="system-overview">System Overview</h2>
<p>Flow&rsquo;s architecture centers on a CLI-first design where the Go-based CLI engine handles all business logic, with other interfaces acting as presentation or integration layers.</p>
<p><img alt="Flow System Overview" loading="lazy" src="/images/flow-system-overview.png"></p>
<p>The desktop app and other interfaces communicate with the CLI through process execution, with each user action spawning a discrete CLI command. Because of this, it&rsquo;s important that CLI operations are fast and efficient, which is achieved through caching and optimized data integrations.</p>
<h3 id="technical-stack">Technical Stack</h3>
<h4 id="core-languages">Core Languages</h4>
<ul>
<li><strong>Go</strong> - CLI engine, business logic, and execution runtime</li>
<li><strong>TypeScript/React</strong> - Desktop frontend with type-safe CLI communication</li>
<li><strong>Rust</strong> - Desktop backend via Tauri framework</li>
</ul>
<h4 id="user-interface">User Interface</h4>
<ul>
<li><strong>Terminal</strong>: <a href="https://github.com/charmbracelet/bubbletea">Bubble Tea</a> with custom <a href="https://github.com/flowexec/tuikit">tuikit</a> component library</li>
<li><strong>Desktop</strong>: <a href="https://tauri.app/">Tauri</a> + <a href="https://mantine.dev/">Mantine UI</a> for VSCode-like interface</li>
<li><strong>CLI</strong>: <a href="https://github.com/spf13/cobra">Cobra</a> for command structure and auto-completion</li>
</ul>
<h4 id="core-libraries">Core Libraries</h4>
<ul>
<li><strong>YAML Processing</strong>: <a href="https://gopkg.in/yaml.v3">yaml.v3</a> for YAML parsing and serialization</li>
<li><strong>Process Management</strong>: <a href="https://github.com/mvdan/sh">mvdan/sh</a> for shell execution</li>
<li><strong>Expression Engine</strong>: Go&rsquo;s <code>text/template</code> + <a href="https://expr-lang.org/">Expr</a> for conditional logic and templating</li>
<li><strong>Markdown Rendering</strong>: <a href="https://github.com/charmbracelet/glamour">Glamour</a> and <a href="https://github.com/remarkjs/react-markdown">react-markdown</a> for auto-generated documentation UI viewers</li>
</ul>
<h2 id="organizational-model">Organizational Model</h2>
<p>Flow&rsquo;s organizational system creates a hierarchical structure that scales from individual projects to complex multi-project ecosystems. The system balances discoverability with isolation, enabling both focused work within projects and cross-project composition.</p>
<h3 id="hierarchy-structure">Hierarchy Structure</h3>
<p><strong>Workspaces</strong> serve as the top-level organizational unit, typically mapping to Git repositories or major project boundaries. Each workspace contains its own configuration, executable discovery rules, and isolated namespace hierarchy.</p>
<p><strong>Namespaces</strong> provide logical grouping within workspaces, similar to packages in programming languages. They enable organizational flexibility — a single workspace might have namespaces for <code>frontend</code>, <code>backend</code>, <code>deploy</code>, or <code>tools</code>. Namespaces are optional but recommended for workspaces with many executables.</p>
<p><strong>Executables</strong> are the atomic units of automation, uniquely identified within their namespace by their name and verb combination. This allows multiple executables with the same name but different purposes (<code>build api</code> vs <code>deploy api</code>).</p>
<h3 id="reference-system">Reference System</h3>
<p>Flow uses a URI-like reference system for executable identification:</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-fallback" data-lang="fallback"><span style="display:flex;"><span>workspace/namespace:name
</span></span><span style="display:flex;"><span>    │         │       │
</span></span><span style="display:flex;"><span>    │         │       └─ Executable name (Optional but unique within verb group + namespace)
</span></span><span style="display:flex;"><span>    │         └───────── Optional namespace grouping
</span></span><span style="display:flex;"><span>    └─────────────────── Workspace boundary
</span></span></code></pre></div><p><strong>Reference Resolution Rules:</strong></p>
<ul>
<li><code>my-task</code> → Current workspace, current namespace, name=&ldquo;my-task&rdquo;</li>
<li><code>backend:api</code> → Current workspace, namespace=&ldquo;backend&rdquo;, name=&ldquo;api&rdquo;</li>
<li><code>project/deploy:prod</code> → workspace=&ldquo;project&rdquo;, namespace=&ldquo;deploy&rdquo;, name=&ldquo;prod&rdquo;</li>
<li><code>project/</code> → workspace=&ldquo;project&rdquo;, no namespace, nameless executable</li>
</ul>
<p><strong>Reference Format Trade-offs:</strong></p>
<ul>
<li><strong>Chosen:</strong> Slightly more verbose for simple cases</li>
<li><strong>Avoided:</strong> Naming collisions, poor tooling support, brittle file/directory coupling</li>
</ul>
<h3 id="verb-system">Verb System</h3>
<p>Verbs describe the action an executable performs while enabling natural language interaction. Verbs can be organized into semantic groups with aliases:</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:#859289;font-style:italic"># Executable definition</span>
</span></span><span style="display:flex;"><span><span style="color:#7a8478">verb</span>: build
</span></span><span style="display:flex;"><span><span style="color:#7a8478">verbAliases</span>: [compile, package, bundle]
</span></span><span style="display:flex;"><span><span style="color:#7a8478">name</span>: my-app
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#859289;font-style:italic"># With the above, all of these commands are equivalent:</span>
</span></span><span style="display:flex;"><span>flow build my-app
</span></span><span style="display:flex;"><span>flow compile my-app
</span></span><span style="display:flex;"><span>flow package my-app
</span></span></code></pre></div><p>This system allows developers to use whichever verb feels most natural while maintaining executable uniqueness through the <code>[verb group + name]</code> constraint.</p>
<p>I&rsquo;ve significantly reduced the number of default verb groups to focus on the most common actions with the most semantic clarity. See the <a href="https://flowexec.io/types/flowfile#executableverb">flow documentation</a> for the latest default list.</p>
<p><img alt="Flow Workspace Tree Example" loading="lazy" src="/images/flow-ws-tree.png"></p>
<h3 id="context-awareness">Context Awareness</h3>
<p>Flow maintains context awareness to reduce typing and improve ergonomics:</p>
<p><strong>Current Workspace Resolution:</strong></p>
<ul>
<li><strong>Dynamic Mode</strong>: Automatically detects workspace based on current directory</li>
<li><strong>Fixed Mode</strong>: Uses explicitly set workspace regardless of location</li>
</ul>
<p><strong>Namespace Scoping:</strong></p>
<ul>
<li>Commands inherit current namespace setting</li>
<li>Explicit namespace references override current context</li>
</ul>
<p><em>Note to self: Explicit command overrides of workspace / namespace may become an emerging need with the Desktop UI and MCP server usage.</em></p>
<h3 id="cross-project-composition">Cross-Project Composition</h3>
<p>The reference system enables powerful cross-project workflows:</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:#7a8478">verb</span>: deploy
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">name</span>: full-stack
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">serial</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">ref</span>: <span style="color:#b2c98f">&#34;build frontend/&#34;</span>     <span style="color:#859289;font-style:italic"># Different workspace</span>
</span></span><span style="display:flex;"><span>        - <span style="color:#7a8478">ref</span>: <span style="color:#b2c98f">&#34;build backend:api&#34;</span>   <span style="color:#859289;font-style:italic"># Different namespace</span>
</span></span><span style="display:flex;"><span>        - <span style="color:#7a8478">ref</span>: <span style="color:#b2c98f">&#34;deploy&#34;</span>              <span style="color:#859289;font-style:italic"># Current context</span>
</span></span></code></pre></div><h2 id="execution-engine">Execution Engine</h2>
<p>The execution engine is the core of Flow, responsible for running executables defined in YAML files.</p>
<h3 id="runner-interface">Runner Interface</h3>
<p>The execution system uses a runner interface pattern where each executable type implements:</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> 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>(
</span></span><span style="display:flex;"><span>		ctx context.Context,
</span></span><span style="display:flex;"><span>		exec <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>		inputArgs []<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:#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>Current runner implementations include:</p>
<ul>
<li><strong>Exec Runner</strong>: Shell command execution</li>
<li><strong>Request Runner</strong>: HTTP request handling</li>
<li><strong>Launch Runner</strong>: Application/URI launching</li>
<li><strong>Render Runner</strong>: Markdown rendering</li>
<li><strong>Serial Runner</strong>: Sequential execution of multiple executables</li>
<li><strong>Parallel Runner</strong>: Concurrent execution with resource limits</li>
</ul>
<h3 id="workflows-serial-and-parallel">Workflows (Serial and Parallel)</h3>
<p>The serial and parallel runners allow for composing complex workflows from simpler executables. Steps are defined with a <code>RefConfig</code> that supports inline commands or references to other executables:</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> SerialRefConfig <span style="color:#e67e80">struct</span> {
</span></span><span style="display:flex;"><span>  Cmd <span style="color:#dbbc7f">string</span>
</span></span><span style="display:flex;"><span>  Ref Ref
</span></span><span style="display:flex;"><span>  Args []<span style="color:#dbbc7f">string</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  If <span style="color:#dbbc7f">string</span>          <span style="color:#859289;font-style:italic">// Expression to conditionally skip the step</span>
</span></span><span style="display:flex;"><span>  Retries <span style="color:#dbbc7f">int</span>
</span></span><span style="display:flex;"><span>  ReviewRequired <span style="color:#dbbc7f">bool</span> <span style="color:#859289;font-style:italic">// Prompts the user before continuing</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Execution and result handling is managed by the internal <code>engine.Engine</code> interface. The <a href="https://github.com/flowexec/flow/tree/main/internal/runner/engine">current implementation</a> includes retry logic, error handling, and result aggregation.</p>
<h3 id="execution-environment-and-state">Execution Environment and State</h3>
<p><strong>Environment Inheritance Hierarchy:</strong></p>
<p>Environment variables are provided to the running executable in the following order:</p>
<ol>
<li>System environment variables (lowest priority)</li>
<li>Dotenv files (<code>.env</code>, workspace-specific)</li>
<li>Flow context variables (<code>FLOW_WORKSPACE_PATH</code>, <code>FLOW_NAMESPACE</code>, etc.)</li>
<li>Executable <code>params</code> (secrets, prompts, static values)</li>
<li>Executable <code>args</code> (command-line arguments)</li>
<li>CLI <code>--param</code> overrides (highest priority)</li>
</ol>
<p><strong>State Management</strong></p>
<p>There are two ways state can be managed when composing workflows:</p>
<ul>
<li>Cache Store: Key-value persistence across executions with scoped lifetime. Values set outside executables persist globally; values set within executables are cleaned up on completion. Uses <a href="https://go.etcd.io/bbolt">bbolt</a> for cross-process storage.</li>
<li>Temporary Directories: Isolated scratch space (<code>f:tmp</code>) with automatic cleanup and shared access across serial/parallel workflow steps.</li>
</ul>
<p><strong>File System Access</strong></p>
<p>By default, the working directory is the directory containing the flow file that defines the executable. This can be configured using special prefixes: <code>//</code> (workspace root), <code>~/</code> (user home), <code>f:tmp</code> (temporary).</p>
<p>There is no automatic sandboxing. Executables inherit full user permissions. <em>Flow assumes users understand their workflows&rsquo; scope and potential for system modification, prioritizing automation flexibility over execution isolation.</em> Containerized execution is a planned future improvement.</p>
<p>See the <a href="https://flowexec.io/guide/executables">executable guide</a> and <a href="https://flowexec.io/guide/advanced?id=managing-state">state management</a> for usage details.</p>
<h2 id="performance-and-caching">Performance and Caching</h2>
<p>Flow uses eager discovery with multi-level caching to keep response times fast. Workspace scanning runs up front and is cached to disk, with in-memory caching layered on top for quick lookups. The cache is invalidated and refreshed via <code>flow sync</code> or the <code>--sync</code> flag.</p>
<p><em>Note to self: Some performance testing needed to validate sub-100ms discovery targets across large workspace trees.</em></p>
<p>For implementation details, see the <a href="https://deepwiki.com/flowexec/flow">DeepWiki reference</a>.</p>
<h2 id="vault-system">Vault System</h2>
<p>The vault system provides secure storage, management, and retrieval of secrets across workspaces and executables. It extends the executable environment with multiple encryption backends.</p>
<p><strong>Implementation</strong>: <a href="https://github.com/flowexec/vault">github.com/flowexec/vault</a></p>
<h3 id="provider-architecture">Provider Architecture</h3>
<p>The vault system supports multiple storage backends through a common <code>Provider</code> interface:</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> Provider <span style="color:#e67e80">interface</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#b2c98f">ID</span>() <span style="color:#dbbc7f">string</span>
</span></span><span style="display:flex;"><span>  <span style="color:#b2c98f">GetSecret</span>(key <span style="color:#dbbc7f">string</span>) (Secret, <span style="color:#dbbc7f">error</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#b2c98f">SetSecret</span>(key <span style="color:#dbbc7f">string</span>, value Secret) <span style="color:#dbbc7f">error</span>
</span></span><span style="display:flex;"><span>  <span style="color:#b2c98f">DeleteSecret</span>(key <span style="color:#dbbc7f">string</span>) <span style="color:#dbbc7f">error</span>
</span></span><span style="display:flex;"><span>  <span style="color:#b2c98f">ListSecrets</span>() ([]<span style="color:#dbbc7f">string</span>, <span style="color:#dbbc7f">error</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#b2c98f">HasSecret</span>(key <span style="color:#dbbc7f">string</span>) (<span style="color:#dbbc7f">bool</span>, <span style="color:#dbbc7f">error</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#b2c98f">Metadata</span>() Metadata
</span></span><span style="display:flex;"><span>  <span style="color:#b2c98f">Close</span>() <span style="color:#dbbc7f">error</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h4 id="current-providers">Current Providers</h4>
<ul>
<li><strong>Unencrypted Provider</strong>: Simple key-value store for development and testing</li>
<li><strong>AES Provider</strong>: Symmetric file encryption using AES-256-GCM (single key management)</li>
<li><strong>Age Provider</strong>: Asymmetric file encryption using the <a href="https://github.com/FiloSottile/age">Age</a> specification (supports multiple recipients)</li>
<li><strong>Keyring Provider</strong>: Uses system keyring (macOS Keychain, Linux Secret Service)</li>
<li><strong>External Provider</strong>: Integration with external CLI tools (1Password, Bitwarden) via command execution</li>
</ul>
<h3 id="vault-switching">Vault Switching</h3>
<p>Vaults can be switched using a context-based system:</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></span><span style="display:flex;"><span>flow secret <span style="color:#d699b6">set</span> api-key <span style="color:#b2c98f">&#34;dev-value&#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-value&#34;</span>
</span></span></code></pre></div><p>Secret references support both current vault context (<code>secretRef: &quot;api-key&quot;</code>) and explicit vault specification (<code>secretRef: &quot;production/api-key&quot;</code>).</p>
<h2 id="template-system">Template System</h2>
<p>Flow includes a templating system for generating executables and workspaces from reusable templates, built on Go&rsquo;s <code>text/template</code> and the <a href="https://expr-lang.org/">Expr</a> expression language. See the <a href="https://flowexec.io">documentation</a> for usage details and examples.</p>
<h2 id="terminal-ui">Terminal UI</h2>
<p>The terminal UI is built on <a href="https://github.com/charmbracelet/bubbletea">Bubble Tea</a> and <a href="https://github.com/charmbracelet/glamour">Glamour</a>, with most views defined in the <a href="https://github.com/flowexec/tuikit">tuikit</a> component library.</p>
<p><img alt="Flow Terminal UI" loading="lazy" src="/images/flow-tui-demo.gif"></p>
<h2 id="desktop-application">Desktop Application</h2>
<p>The desktop experience is evolving into <a href="/architecture/mochi/">Mochi</a>, a local-first DevOps dashboard that wraps Flow&rsquo;s execution engine. See the <a href="/architecture/mochi/">Mochi architecture page</a> for more on where the desktop is headed.</p>
<h2 id="extensions-and-integrations">Extensions and Integrations</h2>
<h3 id="github-actions-and-cicd">GitHub Actions and CI/CD</h3>
<p>Being able to run the same flow executables locally and in CI has always been a goal. A <a href="https://github.com/marketplace/actions/flow-execute">GitHub Action</a> and Docker image are available for use across CI/CD systems. All flowexec organization repositories use this action.</p>
<h3 id="model-context-protocol">Model Context Protocol</h3>
<p>MCP server integration for AI assistant compatibility:</p>
<ul>
<li>Workspace browsing and executable discovery</li>
<li>Natural language workflow generation</li>
<li>Debug assistance with full context</li>
</ul>
<p>Framework chosen: <a href="https://mcp-go.dev/">mcp-go</a></p>
<p>While implementing, I wanted to use <a href="https://modelcontextprotocol.io/specification/2025-06-18/server/resources">MCP resources</a> but support across clients is still low. I implemented the server with just <a href="https://modelcontextprotocol.io/specification/2025-06-18/server/tools">Tools</a> and <a href="https://modelcontextprotocol.io/specification/2025-06-18/server/prompts">Prompts</a> to get the best experience across the most clients.</p>
<p>There is a <code>get_info</code> tool that provides the AI with context about Flow, the current workspace, and expected schemas. This along with server instructions provided the best experience with AI assistants during my testing.</p>
<h2 id="known-limitations-and-future-work">Known Limitations and Future Work</h2>
<ul>
<li><strong><a href="https://github.com/flowexec/flow/issues/185">WASM Plugin System</a></strong>: Allowing Flow to be extended with WebAssembly plugins for custom executable types and template generators.
<ul>
<li><em>Note to self: I did a small POC of this but wasn&rsquo;t very happy with the development experience on the plugin side. WASM may not be the right fit for Flow&rsquo;s extensibility model, but I want to keep it open as a possibility.</em></li>
</ul>
</li>
<li><strong><a href="https://github.com/flowexec/flow/issues/290">AI Runner</a></strong>: Integrating AI models to assist with workflow generation, debugging, and context-aware suggestions.</li>
<li><strong><a href="https://github.com/flowexec/flow/issues/245">Containerized Execution</a></strong>: Running executables in isolated containers for security and resource management.</li>
<li><strong>Current Technical Debt:</strong>
<ul>
<li>No dependency graph analysis (circular reference detection)</li>
</ul>
</li>
</ul>
<h2 id="resources">Resources</h2>
<ul>
<li><a href="https://flowexec.io">Documentation</a></li>
<li><a href="https://deepwiki.com/flowexec/flow">DeepWiki Architecture Reference</a></li>
<li><a href="https://github.com/flowexec/flow">GitHub Repository</a></li>
</ul>
<hr>
<p><em>For implementation details, see the <a href="https://github.com/flowexec/flow">repository</a> and <a href="https://flowexec.io">documentation</a>.</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>Mochi</title>
      <link>https://jahvon.dev/architecture/mochi/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://jahvon.dev/architecture/mochi/</guid>
      <description>Mochi is a local-first DevOps dashboard that auto-discovers your workflows and turns them into a unified, AI-enriched interface. Built on Flow.</description>
      <content:encoded><![CDATA[<style type="text/css">
     
    .notice {
        --title-color: #fff;
        --title-background-color: #6be;
        --content-color: #444;
        --content-background-color: #e7f2fa;
    }

    .notice.info {
        --title-background-color: #fb7;
        --content-background-color: #fec;
    }

    .notice.tip {
        --title-background-color: #5a5;
        --content-background-color: #efe;
    }

    .notice.warning {
        --title-background-color: #c33;
        --content-background-color: #fee;
    }

     
    @media (prefers-color-scheme:dark) {
        .notice {
            --title-color: #fff;
            --title-background-color: #069;
            --content-color: #ddd;
            --content-background-color: #023;
        }

        .notice.info {
            --title-background-color: #a50;
            --content-background-color: #420;
        }

        .notice.tip {
            --title-background-color: #363;
            --content-background-color: #121;
        }

        .notice.warning {
            --title-background-color: #800;
            --content-background-color: #400;
        }
    }

    body.dark .notice {
        --title-color: #fff;
        --title-background-color: #069;
        --content-color: #ddd;
        --content-background-color: #023;
    }

    body.dark .notice.info {
        --title-background-color: #a50;
        --content-background-color: #420;
    }

    body.dark .notice.tip {
        --title-background-color: #363;
        --content-background-color: #121;
    }

    body.dark .notice.warning {
        --title-background-color: #800;
        --content-background-color: #400;
    }

     
    .notice {
        padding: 18px;
        line-height: 24px;
        margin-bottom: 24px;
        border-radius: 4px;
        color: var(--content-color);
        background: var(--content-background-color);
    }

    .notice p:last-child {
        margin-bottom: 0
    }

     
    .notice-title {
        margin: -18px -18px 12px;
        padding: 4px 18px;
        border-radius: 4px 4px 0 0;
        font-weight: 700;
        color: var(--title-color);
        background: var(--title-background-color);
    }

     
    .icon-notice {
        display: inline-flex;
        align-self: center;
        margin-right: 8px;
    }

    .icon-notice img,
    .icon-notice svg {
        height: 1em;
        width: 1em;
        fill: currentColor;
    }

    .icon-notice img,
    .icon-notice.baseline svg {
        top: .125em;
        position: relative;
    }
</style><div class="notice note" >
    <p class="notice-title">
        <span class="icon-notice baseline">
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 128 300 300">
  <path d="M150 128c82.813 0 150 67.188 150 150 0 82.813-67.188 150-150 150C67.187 428 0 360.812 0 278c0-82.813 67.188-150 150-150Zm25 243.555v-37.11c0-3.515-2.734-6.445-6.055-6.445h-37.5c-3.515 0-6.445 2.93-6.445 6.445v37.11c0 3.515 2.93 6.445 6.445 6.445h37.5c3.32 0 6.055-2.93 6.055-6.445Zm-.39-67.188 3.515-121.289c0-1.367-.586-2.734-1.953-3.516-1.172-.976-2.93-1.562-4.688-1.562h-42.968c-1.758 0-3.516.586-4.688 1.563-1.367.78-1.953 2.148-1.953 3.515l3.32 121.29c0 2.734 2.93 4.882 6.64 4.882h36.134c3.515 0 6.445-2.148 6.64-4.883Z"/>
</svg>

        </span>Note</p><p>Mochi is in early access. Join the waitlist at <a href="https://mochiexec.io">mochiexec.io</a>.</p></div>

<p>Mochi is a local-first DevOps dashboard built on top of <a href="https://flowexec.io">Flow</a>. Point it at a directory and it finds your development workflows and turns them into a unified, AI-enriched interface.</p>
<h2 id="design-philosophy">Design Philosophy</h2>
<ul>
<li><strong>Local-first</strong>: No cloud sync, no external accounts required. Your workflows never leave your machine.</li>
<li><strong>Built on Flow</strong>: Mochi inherits Flow&rsquo;s execution engine, secret management, and composability model rather than building a parallel system from scratch.</li>
<li><strong>AI-enriched, not AI-dependent</strong>: AI augments the experience with workflow analysis and suggestions, but the tool works without it.</li>
<li><strong>Unified discovery</strong>: Meet developers where they are instead of requiring migration to a new workflow format.</li>
</ul>
<h2 id="system-overview">System Overview</h2>
<p>Mochi wraps Flow&rsquo;s CLI engine as its execution runtime. The desktop app and CLI share the same binary, with the desktop communicating through process execution in the same pattern as <a href="/architecture/flow/#system-overview">Flow&rsquo;s architecture</a>. Discovery runs at startup and on sync, after which all discovered executables are available in both the dashboard and CLI identically.</p>
<p>The desktop is not yet released, but the architecture is taking shape. Here&rsquo;s a preview:</p>
<video autoplay loop muted playsinline width="100%">
  <source src="/images/flow-desktop-demo.mp4" type="video/mp4">
</video>
<h2 id="auto-discovery-engine">Auto-Discovery Engine</h2>
<p>Mochi&rsquo;s discovery layer imports executables from common developer workflow files without requiring any configuration changes.</p>
<p>Discovered executables are treated identically to native Flow executables once imported: they appear in the browse interface, support the same reference patterns, and can be composed into larger workflows. The sync step surfaces naming conflicts clearly so nothing is silently overwritten.</p>
<p>This discovery system is built on the same import infrastructure in Flow&rsquo;s core. See the <a href="/architecture/flow/">Flow architecture</a> for implementation details.</p>
<h2 id="ai-enrichment-layer">AI Enrichment Layer</h2>
<p>Mochi connects to an AI provider for workflow analysis, troubleshooting suggestions, and workflow context. You can use Anthropic&rsquo;s cloud API or a local <a href="https://ollama.com">Ollama</a> instance depending on your preference and privacy requirements.</p>
<p>The enrichment layer is optional and additive. The dashboard is fully functional without it, and workflow definitions are processed locally before anything is sent externally.</p>
<h2 id="desktop-and-cli-architecture">Desktop and CLI Architecture</h2>
<p>Mochi ships as a single binary that includes both the CLI and the Tauri desktop backend. I chose Tauri because it was the most interesting option at the time, given my interest in learning Rust and building a desktop app without the overhead of Electron. The frontend is TypeScript/React with Mantine UI.</p>
<p>Type safety across Go, TypeScript, and Rust is maintained through code generation from a shared JSON schema. Schema changes propagate to all three languages automatically during the build, and the same schema generates documentation as a side effect. This has been one of the best quality-of-life decisions in the project — make a change in one place and have confidence everything stays consistent.</p>
<p><img alt="Docs and Code Generation" loading="lazy" src="/images/flow-gen.png"></p>
<h2 id="resources">Resources</h2>
<ul>
<li><a href="https://mochiexec.io">mochiexec.io</a></li>
<li><a href="/architecture/flow/">Flow Architecture</a></li>
<li><a href="https://github.com/flowexec">GitHub (flowexec org)</a></li>
</ul>
]]></content:encoded>
    </item>
  </channel>
</rss>
