[{"content":"Last fall I wrote about using AI as a creative partner 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.\nCreative work you experience once. Development work you live in. That difference changes what you need from a partner.\nThe Bench When I was at Recurse Center 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. Flow was the natural workbench. It&rsquo;s my own tool and I know every corner of it.\nOver 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.\nThat changes what I actually delegate.\nThe Delegation Model 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.\nA concrete example: I&rsquo;ve been building an AI proxy backed by Cloudflare AI Gateway that sits across all of my tools. I decided on the architecture, what the proxy needs to do, how it integrates with the Cloudflare platform, 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.\nWhat makes this work is having a single place to see everything. Everything I&rsquo;ve configured, discoverable from one surface.\nOne 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.\nI&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.\nLicklider&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.\nWhat It Produced 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.\nA few months of this produced Flow v2 and something I&rsquo;ve been sitting on: Mochi. 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.\nExecutables view. Everything Mochi found across my workspaces, tagged and filterable.\nStill early. If it sounds useful, the waitlist is at mochiexec.io.\nI&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.\n","permalink":"https:\/\/jahvon.dev\/notes\/ai-development-partner\/","summary":"Reflections on how I&rsquo;ve been using AI as a development partner - what I delegate, what I don&rsquo;t, and what it&rsquo;s produced.","title":"AI as a Development Partner"},{"content":"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.\nThe 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.\nAt 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:\n[[services]] binding = &#34;BOUND_SERVICE&#34; service = &#34;my-worker&#34; Then in code I can easily send a request like this, with no HTTP overhead:\nconst request = new Request(&#39;api\/v1\/data&#39;, { method: &#39;GET&#39; }); const response = await env.BOUND_SERVICE.fetch(request); 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.\nI 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:\n# In the producer&#39;s wrangler.toml [[queues.producers]] queue = &#34;message-queue&#34; binding = &#34;MESSAGE_QUEUE&#34; # In the consumer&#39;s wrangler.toml [[queues.consumers]] queue = &#34;message-queue&#34; max_concurrency = 5 max_batch_size = 3 max_batch_timeout = 5 max_retries = 3 dead_letter_queue = &#34;message-dlq&#34; Then in code I just end up with code blocks like this:\n\/\/ Sending messages const msg = { key: &#39;value&#39; }; await env.MESSAGE_QUEUE.send(msg); \/\/ Processing message export default { async queue(batch, env, ctx): Promise&lt;void&gt; { for (const message of batch.messages) { console.log(message.key); } } }; 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.\nOne 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.\nThe 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.\n\/\/ The container creates a &#34;Durable Object&#34; export class MyContainer extends Container { defaultPort = 8080; envVars = { ENV_KEY1: env.ENV_KEY1 || &#39;&#39;, ENV_KEY2: &#39;hello world&#39; }; } \/\/ Send requests to the container const container = env.CONTAINER.getByName(&#39;instance-1&#39;); container.fetch(&#39;api\/v1\/action&#39;, { method: &#39;POST&#39;, body: JSON.stringify(request), }); Containers (and durable objects, 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.\nThe 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 limits worth knowing too, but I rarely hit them when following the patterns that help me get around those other challenges.\nI&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.\nThere 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 Drizzle, Hono, and Localflare 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.\nComing 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.\n","permalink":"https:\/\/jahvon.dev\/notes\/cloudflare-experience\/","summary":"Notes from a few months building on Cloudflare Workers after years of Kubernetes.","title":"From Cloud Native to Serverless with Cloudflare"},{"content":"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, Boston Higher Education Resource Center (HERC). 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 Catchafire - 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.\nI 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.\nStaying involved 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.\nSince 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!\nRedesign goals 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.\nMy focus for this redesign was on three key areas:\nStorytelling that matches their identity: Softening the overall feel of the site that allows the stories of their community and students to shine through more naturally.\nData-driven messaging: 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.\nStreamlined user journeys: 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.\nThere&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.\nTechnical approach 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 underscores 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.\nFor 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 Elementor 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.\nI 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.\nYou can see the live transformation at bostonherc.org.\nIf their mission speaks to you too, consider giving to this great organization!\n","permalink":"https:\/\/jahvon.dev\/notes\/herc-journey\/","summary":"My experience redesigning Boston HERC&rsquo;s website to reflect their growth and impact supporting first-generation students in the Boston area.","title":"Building Boston HERC's Website, Twice"},{"content":"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. 1\nMy 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.\nWhat We Built The Collaborative Illusion 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 Hearing Birdsong 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.\nI 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.\nReading about what Joseph Weizenbaum wrote in Contextual Understandings by Computers about ELIZA, his 1960s chatbot, a few weeks later reminded me of this experience:\nOne 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.\nThese 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.\nDrawing the Line 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.\nI 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.\nIn 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.\nMan-Computer Symbiosis, J. C. Licklider\nLicklider&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.\nWhat We Uncovered Vibe Coding in Practice 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.\nTo give you a sense of what I was able to create with AI, I vibe coded this interactive portfolio:\nWe 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.\nThe Efficiency Trap 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.\nKate Crawford argues in The Atlas of AI 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.\nThe 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.\nDesigning Dependency 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?\nCrawford 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.\nI&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?\nCrawford 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.\nSure, 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.\nA Working Philosophy 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.\nA 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.\nAI as a Learning Partner 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.\nI&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.\nCheck out RC&rsquo;s position on AI 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;&#x21a9;&#xfe0e;\n","permalink":"https:\/\/jahvon.dev\/notes\/ai-creative-partner\/","summary":"An essay reflecting on my time using GenAI as a creative partner for a HGSE course that I was a teaching fellow for.","title":"AI as a Creative Partner"},{"content":"I wrapped up my batch at Recurse Center 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.\nLearning highlights Collaborative learning through pairing - 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.\nTeaching through presentation - 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 final presentation deck for the overview that I gave on flow&rsquo;s composable workflows!\nDiscovering my learning patterns - 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.\nBalancing focus and community - 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.\nCore project As I described in 6 Weeks of flow at Recurse Center, I decided to expand my long-running side project flow 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.\nflow CLI v1.0 release - Stabilized platform with comprehensive testing, v2 of the integrated secrets vault, major documentation updates, and CI integration via custom GitHub Action\nDesktop app POC - Built proof-of-concept with Tauri, establishing CLI-as-source-of-truth architecture for upcoming first release\nExecutable generation - Added parsers for makefile, package.json, and docker-compose to streamline workflow migration\nMCP server - Built Model Context Protocol integration enabling AI tools to understand flow workspaces and executables natively\nCheck out my new architecture doc 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.\nHonorable mentions Dev environment exploration - 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.\n&ldquo;Impossible&rdquo; experiments - 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.\nCreative coding - 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 this Codepen for an example of one of my creations.\nVibe coding - 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.\nCommunity programming - 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.\nWordPress project - 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 bostonherc.org. It was interesting finding ways to apply my evolving learning style to evaluating and picking up newer, no-code frameworks like Elementor.\nMy 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.\n","permalink":"https:\/\/jahvon.dev\/notes\/rc-return-statement\/","summary":"What I built and learned during 12 weeks of focused development at the Recurse Center.","title":"return recurse(): Summer of Building and Learning"},{"content":"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 I 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.\nI introduced flow in another post a few months ago, but I&rsquo;ve had the amazing opportunity to start a sabbatical at the Recurse Center, 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.\nI&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.\nflow desktop 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. Bubble Tea 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.\nComing 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.\nArchitecture Decision: CLI as Single Source of Truth\nInstead of duplicating business logic in my desktop app, I&rsquo;ve been building it as a pure visualization layer over the existing CLI.\nThis 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.\nTech Stack\nAll of the resources, pairing, feedback, and individual research I&rsquo;ve done has landed me on the following:\nTauri: Gives me Rust backend + web frontend without Electron&rsquo;s bloat. Bonus that it&rsquo;s an opportunity to learn some Rust. TypeScript: 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. React &amp; Mantine UI: VSCode-like components without building everything from scratch. Their Spotlight extension is what sold me - it could be a really cool search and command center for the UI! Demo!\nHere 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.\nI 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.\nvaults v2 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.\nI 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 this repo. From the flow perspective, the experience would be like this:\nCreating a vault\n# Auto-generates everything for the AES vault flow vault create development # Create an Age vault with identity generated from age-keygen flow vault create team --type age --recipients key1,key2,key3 --identityFile id.txt # External needs CLI integration flow vault create bitwarden --type external --interactive Vault Switching as Primary UX\nBorrowed the mental model from git\/kubectl:\nflow vault switch development # Like git checkout flow secret set api-key &#34;dev-123&#34; flow vault switch production flow secret set api-key &#34;prod-456&#34; This allows for clean secret references in executables: secretRef: &quot;api-key&quot; uses current vault, secretRef: &quot;production\/api-key&quot; is explicit.\nTechnical decisions Executable composition Executables aren&rsquo;t just scripts - they&rsquo;re composable units with conditional logic:\nserial: failFast: true execs: - if: os == &#34;darwin&#34; cmd: &#34;command -v mytool || brew install mytool&#34; - if: env[&#34;PUSH&#34;] == &#34;true&#34; cmd: make image - ref: deploy development reviewRequired: true # Pauses for human confirmation - ref: launch app The expression language (using Expr) has access to OS info, environment variables, and flow&rsquo;s cache. It&rsquo;s like having bash conditionals but declarative.\nProcess architecture The desktop app&rsquo;s process model is pretty simple. Each user action spawns a CLI process:\n#[tauri::command] async fn get_workspaces() -&gt; Result&lt;Vec&lt;Workspace&gt;, String&gt; { let output = Command::new(&#34;flow&#34;) .args([&#34;workspace&#34;, &#34;list&#34;, &#34;--output&#34;, &#34;json&#34;]) .output().await?; serde_json::from_slice(&amp;output.stdout) } This seems inefficient but has huge benefits:\nDesktop crashes don&rsquo;t corrupt CLI state CLI updates immediately benefit desktop No state synchronization between processes Easy to debug - each operation is a discrete CLI command RC moments that shaped the code Pair Programming: 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.\nThe Feedback Loop: 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 issue I closed.\nCommunity Inspiration: 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.\nNext up flow MCP server 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:\nBrowsing flow files and suggesting syntax improvements Generating new workflows from natural language Debugging failures with full workspace context \ud83d\ude80 WASM plugin system Extending flow with WASM-integrated extensions. I want to try to allow automations to be programmable in two ways:\nExecutable template generator: Plugins that run template generation for flow-discoverable executables (from APIs, templates, external sources). I&rsquo;m thinking of something like Taskfile\/just \u2192 flow executable integrations to start WASM Runtime executable type: Plugin executables that run sandboxed through flow 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.\nTest &amp; release improvements The best way to try out some of these new and upcoming features would be to clone the flow repo and run the build binary executable to get a local go build. Note that the main branch is not guaranteed to be stable, though.\nWith 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.\n","permalink":"https:\/\/jahvon.dev\/notes\/rc-6-week-flow\/","summary":"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.","title":"6 Weeks of flow at Recurse Center"},{"content":"A few months back, I worked through VictoriaMetrics&rsquo; Go concurrency series and wanted to get some practice. So I implemented a few distributed systems, work distribution patterns to see how the concurrency patterns translate.\nWork 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.\nProducer-Consumer: Async Work Distribution Producers generate work and send it through channels while consumers process it asynchronously.\ntype ConsumerResult struct { ConsumerID int Data string } \/\/ start multiple consumers for id := 0; id &lt; numConsumers; id++ { wg.Add(1) go func(consumerID int) { defer wg.Done() for msg := range msgChan { result := ConsumerResult{ ConsumerID: consumerID, Data: fmt.Sprintf(&#34;processed-%d&#34;, msg), } resultChan &lt;- result } }(id) } \/\/ start a single producer that sends work into a channel go func() { defer close(msgChan) for i := 1; i &lt;= 25; i++ { msgChan &lt;- i } }() Buffered channels give you throttling - if consumers can&rsquo;t keep up, the producer blocks instead of consuming memory.\nUse this pattern for event streaming, async processing, or decoupling generation speed from processing speed. It maps directly to Kafka or microservice event handling.\nWorker Pools: Controlled Work Distribution 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.\ntype PoolJob struct { ID int Data string } \/\/ start a fixed number of workers for i := 0; i &lt; numWorkers; i++ { wg.Add(1) go func(workerID int) { defer wg.Done() for job := range jobChan { \/\/ do some work time.Sleep(100 * time.Millisecond) results &lt;- PoolResult{ WorkerID: workerID, JobID: job.ID, Value: fmt.Sprintf(&#34;processed-%s&#34;, job.Data), } } }(i) } The job channel acts like a load balancer - work goes to whichever worker is available.\nThis 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.\nBatch Processing: Efficient Work Distribution 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.\nfunc (p *BatchProcessor) startBatchAggregator() { go func() { batch := make([]int, 0, p.batchSize) flushTimer := time.NewTimer(2 * time.Second) sendBatch := func() { &lt;-p.rateLimiter.C \/\/ wait for rate limiter batchCopy := make([]int, len(batch)) copy(batchCopy, batch) p.batchChan &lt;- batchCopy batch = batch[:0] } for { select { case item, ok := &lt;-p.itemChan: if !ok { if len(batch) &gt; 0 { sendBatch() } close(p.batchChan) return } batch = append(batch, item) if len(batch) &gt;= p.batchSize { sendBatch() } case &lt;-flushTimer.C: if len(batch) &gt; 0 { sendBatch() } } } }() } The select 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.\nThe batch processing pattern works well for database bulk operations, API integrations with rate limits, or protecting downstream services.\nA Few Notes Working through these patterns reinforced a few things:\nChannels behave like message queues with capacity limits and natural flow control. Multiple goroutines running the same function is basically horizontal scaling - same patterns you&rsquo;d use for scaling system components. These patterns compose well. Producer-consumer provides the foundation, worker pools add structure, batching adds efficiency. Pattern Analogy Use Case Producer-Consumer Message queues, event streams Event-driven architectures, async processing Worker Pools Load-balanced system components Controlled concurrency, predictable resources Batch Processing ETL pipelines, bulk APIs Rate limiting, bulk operations Error Handling 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:\ntype WorkResult struct { Data string Err error } func worker(jobs &lt;-chan int, results chan&lt;- WorkResult) { for job := range jobs { if job%7 == 0 { \/\/ simulate some failures results &lt;- WorkResult{Err: fmt.Errorf(&#34;job %d failed&#34;, job)} continue } results &lt;- WorkResult{Data: fmt.Sprintf(&#34;processed-%d&#34;, job)} } } For timeouts and cancellation, context.Context works well:\nfunc workerWithTimeout(ctx context.Context, jobs &lt;-chan int, results chan&lt;- WorkResult) { for { select { case job := &lt;-jobs: \/\/ process the job case &lt;-ctx.Done(): results &lt;- WorkResult{Err: ctx.Err()} return } } } Beyond the basics These patterns scratch the surface of Go&rsquo;s concurrency toolkit. The VictoriaMetrics series I mentioned dives deep into more advanced primitives like sync.Mutex for protecting shared state, sync.Pool for object reuse, sync.Once for one-time initialization, and sync.Map for concurrent map access. I recommend checking it out if you haven&rsquo;t already!\nI 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.\nAs 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.\nFull code examples on GitHub\nNext: circuit breaker and fan-in\/fan-out implementations.\n","permalink":"https:\/\/jahvon.dev\/notes\/distributing-work\/","summary":"<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>\n<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>","title":"Distributing Work with Go Concurrency"},{"content":"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. (side note: Check out this blog post I wrote on how we supercharged the experience for our Product Engineers)\nHowever, the challenges that I 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!\nEnter 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 Taskfile or Just, 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 you desire; making them easily discoverable, automated, and observable.\nIt&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.\nBuilding the Foundation My first build of flow centered around two simple concepts driven by YAML files:\nWorkspaces for organizing tasks across projects\/repos Executables for defining those tasks I included a simple tview 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.\nThrough usage, I found myself iterating a lot. 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 bubbletea TUI framework, an internal documentation generator for the flowexec.io site, the templating workflow, and the state and conditional 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.\nAt 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 exec executable type to define the command that&rsquo;s run:\nnamespace: authentik # optional, additional grouping in a workspace tags: [k8s, auth] # useful for filtering the `flow library` command # this description is rendered as markdown (alongside other executable info) # when viewing in the `flow library`. description: | **References:** - https:\/\/goauthentik.io\/ - https:\/\/github.com\/goauthentik\/helm executables: # flow install authentik:app - verb: install name: app aliases: [chart] description: Upgrade\/install Authentik Helm chart exec: params: # secrets are managed with the integrated vault via the `flow secret` command - secretRef: authentik-secret-key envKey: AUTHENTIK_SECRET_KEY - secretRef: authentik-db-password envKey: AUTHENTIK_DB_PASSWORD cmd: | helm upgrade --install authentik authentik\/authentik \\ --version 2024.10.4 \\ --namespace auth --create-namespace \\ --set authentik.secret_key=$AUTHENTIK_SECRET_KEY \\ --set authentik.postgresql.password=$AUTHENTIK_DB_PASSWORD \\ --set postgresql.auth.password=$AUTHENTIK_DB_PASSWORD \\ --set postgresql.postgresqlPassword=$AUTHENTIK_DB_PASSWORD \\ -f values.yaml The flow CLI provides a consistent experience for all executable runs and searches. This includes automatically generating a summary markdown document viewable with the flow library command, log formatting and archiving, and a configurable TUI experience.\nThis will show up in the flow library as rendered markdown:\nAs my needs evolved, I added more executable configurations and types. Here&rsquo;s an example of a common request executable I use to pause my home&rsquo;s pi.hole blocking:\nexecutables: # flow pause pihole - verb: pause name: pihole request: method: &#34;POST&#34; args: - pos: 1 envKey: DURATION default: 300 type: int params: - secretRef: pihole-pwhash envKey: PWHASH url: http:\/\/pi.hole\/admin\/api.php?disable=$DURATION&amp;auth=$PWHASH validStatusCodes: [200] logResponse: true transformResponse: if .status == &#34;disabled&#34; then .status = &#34;paused&#34; else . end Here&rsquo;s an example of the log output:\nLessons Learned 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:\nSmall Packages and Interfaces Made Testing a Breeze This approach makes testing easier, improves code organization, and makes refactoring as ideas evolve a joy.\nEach 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 Runner:\n\/\/go:generate mockgen -destination=mocks\/mock_runner.go -package=mocks github.com\/jahvon\/flow\/internal\/runner Runner type Runner interface { Name() string Exec(ctx *context.Context, e *executable.Executable, eng engine.Engine, inputEnv map[string]string) error IsCompatible(executable *executable.Executable) bool } This interface allows me to easily add new executable types by just implementing these three methods. The core execution logic remains clean and extensible:\nfunc Exec( ctx *context.Context, executable *executable.Executable, eng engine.Engine, inputEnv map[string]string, ) error { var assignedRunner Runner for _, runner := range registeredRunners { if runner.IsCompatible(executable) { assignedRunner = runner break } } if assignedRunner == nil { return fmt.Errorf(&#34;compatible runner not found for executable %s&#34;, executable.ID()) } if executable.Timeout == 0 { return assignedRunner.Exec(ctx, executable, eng, inputEnv) } done := make(chan error, 1) go func() { done &lt;- assignedRunner.Exec(ctx, executable, eng, inputEnv) }() select { case err := &lt;-done: return err case &lt;-time.After(executable.Timeout): return fmt.Errorf(&#34;timeout after %v&#34;, executable.Timeout) } } For testing, I use ginkgo for its expressive BDD-style syntax and GoMock 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.\nBuild Better Abstractions with Service Layers 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.\nFor example, my run service abstracts away the complexities of running shell operations with the github.com\/mvdan\/sh 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 source code.\nGood Tools Are Worth the Investment 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 go-jsonschema for codegen.\nHere&rsquo;s an example of how my Launch executable type is defined:\nLaunchExecutableType: type: object required: [uri] description: Launches an application or opens a URI. properties: params: $ref: &#39;#\/definitions\/ParameterList&#39; args: $ref: &#39;#\/definitions\/ArgumentList&#39; app: type: string description: The application to launch the URI with. default: &#34;&#34; uri: type: string description: The URI to launch. This can be a file path or a web URL. default: &#34;&#34; wait: type: boolean description: If set to true, the executable will wait for the launched application to exit before continuing. default: false This generates both the Go type and its documentation:\n\/\/ Launches an application or opens a URI. type LaunchExecutableType struct { \/\/ The application to launch the URI with. App string `json:&#34;app,omitempty&#34; yaml:&#34;app,omitempty&#34; mapstructure:&#34;app,omitempty&#34;` \/\/ Args corresponds to the JSON schema field &#34;args&#34;. Args ArgumentList `json:&#34;args,omitempty&#34; yaml:&#34;args,omitempty&#34; mapstructure:&#34;args,omitempty&#34;` \/\/ Params corresponds to the JSON schema field &#34;params&#34;. Params ParameterList `json:&#34;params,omitempty&#34; yaml:&#34;params,omitempty&#34; mapstructure:&#34;params,omitempty&#34;` \/\/ The URI to launch. This can be a file path or a web URL. URI string `json:&#34;uri&#34; yaml:&#34;uri&#34; mapstructure:&#34;uri&#34;` \/\/ If set to true, the executable will wait for the launched application to exit \/\/ before continuing. Wait bool `json:&#34;wait,omitempty&#34; yaml:&#34;wait,omitempty&#34; mapstructure:&#34;wait,omitempty&#34;` } I&rsquo;ve also built a docsgen 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 here.\nThis 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.\nThe Road Ahead 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.\nflow 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 contribute, star the repo to show your support, and to open issues to report bugs or suggest features!\n","permalink":"https:\/\/jahvon.dev\/notes\/forging-flow\/","summary":"<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>\n<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>","title":"Forging flow: My Journey of Creation and Learning"},{"content":"\ud83d\udc4b\ud83c\udffe Hey there! I&rsquo;m Jahvon, a software and platform engineer based in Boston. My work tends to cluster around developer tooling and cloud-native systems, but I follow my curiosity into a lot of different spaces. That has taken me through teaching, community work, and more than a few projects I couldn&rsquo;t fully explain when I started them.\nI build things to understand them. This site is a record of what I&rsquo;m working on, what I&rsquo;m figuring out, and occasionally what I got wrong.\nCheck out my latest lab note: AI as a Development Partner Where I&rsquo;ve Been Founding Engineer at Klipster (2025 - now)\nBuilding an AI-enriched video studio and automation platform for an early-stage Proptech startup.\nDockery Labs (2025 - now)\nMy personal R&amp;D space for building and experimenting with new ideas.\nTeaching Fellow at Harvard Graduate School of Education (2025)\nTeaching and learning through &ldquo;Vibe Coding,&rdquo; a module exploring generative AI as a creative partner in programming.\nRecurse Center (2025)\nTook a sabbatical to deepen my coding practice across multiple languages and frameworks, with a focus on flow - my open-source developer automation platform.\nDeveloper Experience at CarGurus (2022-2025)\nBuilt Kubernetes controllers, deployment pipelines, and developer tools (like Mach5) used by engineering teams across the entire product development organization.\nKubernetes Networking at solo.io (2021-2022)\nWorked on open-source release pipelines and Kubernetes controllers for Istio service mesh and Envoy-based API gateway technologies.\nEdTech Data Platform at Panorama Education (2018-2021)\nBuilt and optimized ETL pipelines that transform school data into actionable insights for improving student outcomes.\nYou can also find me on LinkedIn and GitHub.\nLab Notes Architecture ","permalink":"https:\/\/jahvon.dev\/about\/","summary":"about","title":"About Me"},{"content":" Note\nThis page covers the architectural decisions and design rationale behind flow. For a deep technical reference, see the architecture docs on DeepWiki.\nLast updated: May 2026\nFlow 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.\nDesign Philosophy Developer-Centric: Flow should be designed with developers in mind, providing a powerful yet intuitive interface for managing automation tasks. Composable: Flow should allow users to compose complex workflows from simple, reusable executables, enabling cross-project automation and collaboration. Discoverable: Flow should make it easy to find and run executables across multiple projects and workspaces, with a focus on discoverability and usability. Local-first: Flow should be able to be run entirely on a local machine, with no external dependencies or cloud services required. Malleable: Flow should be easily extensible and customizable, allowing users to adapt it to their specific needs and use it in a variety of contexts. System Overview 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.\nThe 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.\nTechnical Stack Core Languages Go - CLI engine, business logic, and execution runtime TypeScript\/React - Desktop frontend with type-safe CLI communication Rust - Desktop backend via Tauri framework User Interface Terminal: Bubble Tea with custom tuikit component library Desktop: Tauri + Mantine UI for VSCode-like interface CLI: Cobra for command structure and auto-completion Core Libraries YAML Processing: yaml.v3 for YAML parsing and serialization Process Management: mvdan\/sh for shell execution Expression Engine: Go&rsquo;s text\/template + Expr for conditional logic and templating Markdown Rendering: Glamour and react-markdown for auto-generated documentation UI viewers Organizational Model 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.\nHierarchy Structure Workspaces 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.\nNamespaces provide logical grouping within workspaces, similar to packages in programming languages. They enable organizational flexibility \u2014 a single workspace might have namespaces for frontend, backend, deploy, or tools. Namespaces are optional but recommended for workspaces with many executables.\nExecutables 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 (build api vs deploy api).\nReference System Flow uses a URI-like reference system for executable identification:\nworkspace\/namespace:name \u2502 \u2502 \u2502 \u2502 \u2502 \u2514\u2500 Executable name (Optional but unique within verb group + namespace) \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Optional namespace grouping \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Workspace boundary Reference Resolution Rules:\nmy-task \u2192 Current workspace, current namespace, name=&ldquo;my-task&rdquo; backend:api \u2192 Current workspace, namespace=&ldquo;backend&rdquo;, name=&ldquo;api&rdquo; project\/deploy:prod \u2192 workspace=&ldquo;project&rdquo;, namespace=&ldquo;deploy&rdquo;, name=&ldquo;prod&rdquo; project\/ \u2192 workspace=&ldquo;project&rdquo;, no namespace, nameless executable Reference Format Trade-offs:\nChosen: Slightly more verbose for simple cases Avoided: Naming collisions, poor tooling support, brittle file\/directory coupling Verb System Verbs describe the action an executable performs while enabling natural language interaction. Verbs can be organized into semantic groups with aliases:\n# Executable definition verb: build verbAliases: [compile, package, bundle] name: my-app # With the above, all of these commands are equivalent: flow build my-app flow compile my-app flow package my-app This system allows developers to use whichever verb feels most natural while maintaining executable uniqueness through the [verb group + name] constraint.\nI&rsquo;ve significantly reduced the number of default verb groups to focus on the most common actions with the most semantic clarity. See the flow documentation for the latest default list.\nContext Awareness Flow maintains context awareness to reduce typing and improve ergonomics:\nCurrent Workspace Resolution:\nDynamic Mode: Automatically detects workspace based on current directory Fixed Mode: Uses explicitly set workspace regardless of location Namespace Scoping:\nCommands inherit current namespace setting Explicit namespace references override current context Note to self: Explicit command overrides of workspace \/ namespace may become an emerging need with the Desktop UI and MCP server usage.\nCross-Project Composition The reference system enables powerful cross-project workflows:\nexecutables: - verb: deploy name: full-stack serial: execs: - ref: &#34;build frontend\/&#34; # Different workspace - ref: &#34;build backend:api&#34; # Different namespace - ref: &#34;deploy&#34; # Current context Execution Engine The execution engine is the core of Flow, responsible for running executables defined in YAML files.\nRunner Interface The execution system uses a runner interface pattern where each executable type implements:\ntype Runner interface { Name() string Exec( ctx context.Context, exec *executable.Executable, eng engine.Engine, inputEnv map[string]string, inputArgs []string, ) error IsCompatible(executable *executable.Executable) bool } Current runner implementations include:\nExec Runner: Shell command execution Request Runner: HTTP request handling Launch Runner: Application\/URI launching Render Runner: Markdown rendering Serial Runner: Sequential execution of multiple executables Parallel Runner: Concurrent execution with resource limits Workflows (Serial and Parallel) The serial and parallel runners allow for composing complex workflows from simpler executables. Steps are defined with a RefConfig that supports inline commands or references to other executables:\ntype SerialRefConfig struct { Cmd string Ref Ref Args []string If string \/\/ Expression to conditionally skip the step Retries int ReviewRequired bool \/\/ Prompts the user before continuing } Execution and result handling is managed by the internal engine.Engine interface. The current implementation includes retry logic, error handling, and result aggregation.\nExecution Environment and State Environment Inheritance Hierarchy:\nEnvironment variables are provided to the running executable in the following order:\nSystem environment variables (lowest priority) Dotenv files (.env, workspace-specific) Flow context variables (FLOW_WORKSPACE_PATH, FLOW_NAMESPACE, etc.) Executable params (secrets, prompts, static values) Executable args (command-line arguments) CLI --param overrides (highest priority) State Management\nThere are two ways state can be managed when composing workflows:\nCache 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 bbolt for cross-process storage. Temporary Directories: Isolated scratch space (f:tmp) with automatic cleanup and shared access across serial\/parallel workflow steps. File System Access\nBy default, the working directory is the directory containing the flow file that defines the executable. This can be configured using special prefixes: \/\/ (workspace root), ~\/ (user home), f:tmp (temporary).\nThere is no automatic sandboxing. Executables inherit full user permissions. Flow assumes users understand their workflows&rsquo; scope and potential for system modification, prioritizing automation flexibility over execution isolation. Containerized execution is a planned future improvement.\nSee the executable guide and state management for usage details.\nPerformance and Caching 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 flow sync or the --sync flag.\nNote to self: Some performance testing needed to validate sub-100ms discovery targets across large workspace trees.\nFor implementation details, see the DeepWiki reference.\nVault System The vault system provides secure storage, management, and retrieval of secrets across workspaces and executables. It extends the executable environment with multiple encryption backends.\nImplementation: github.com\/flowexec\/vault\nProvider Architecture The vault system supports multiple storage backends through a common Provider interface:\ntype Provider interface { ID() string GetSecret(key string) (Secret, error) SetSecret(key string, value Secret) error DeleteSecret(key string) error ListSecrets() ([]string, error) HasSecret(key string) (bool, error) Metadata() Metadata Close() error } Current Providers Unencrypted Provider: Simple key-value store for development and testing AES Provider: Symmetric file encryption using AES-256-GCM (single key management) Age Provider: Asymmetric file encryption using the Age specification (supports multiple recipients) Keyring Provider: Uses system keyring (macOS Keychain, Linux Secret Service) External Provider: Integration with external CLI tools (1Password, Bitwarden) via command execution Vault Switching Vaults can be switched using a context-based system:\nflow vault switch development flow secret set api-key &#34;dev-value&#34; flow vault switch production flow secret set api-key &#34;prod-value&#34; Secret references support both current vault context (secretRef: &quot;api-key&quot;) and explicit vault specification (secretRef: &quot;production\/api-key&quot;).\nTemplate System Flow includes a templating system for generating executables and workspaces from reusable templates, built on Go&rsquo;s text\/template and the Expr expression language. See the documentation for usage details and examples.\nTerminal UI The terminal UI is built on Bubble Tea and Glamour, with most views defined in the tuikit component library.\nDesktop Application The desktop experience is evolving into Mochi, a local-first DevOps dashboard that wraps Flow&rsquo;s execution engine. See the Mochi architecture page for more on where the desktop is headed.\nExtensions and Integrations GitHub Actions and CI\/CD Being able to run the same flow executables locally and in CI has always been a goal. A GitHub Action and Docker image are available for use across CI\/CD systems. All flowexec organization repositories use this action.\nModel Context Protocol MCP server integration for AI assistant compatibility:\nWorkspace browsing and executable discovery Natural language workflow generation Debug assistance with full context Framework chosen: mcp-go\nWhile implementing, I wanted to use MCP resources but support across clients is still low. I implemented the server with just Tools and Prompts to get the best experience across the most clients.\nThere is a get_info 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.\nKnown Limitations and Future Work WASM Plugin System: Allowing Flow to be extended with WebAssembly plugins for custom executable types and template generators. 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. AI Runner: Integrating AI models to assist with workflow generation, debugging, and context-aware suggestions. Containerized Execution: Running executables in isolated containers for security and resource management. Current Technical Debt: No dependency graph analysis (circular reference detection) Resources Documentation DeepWiki Architecture Reference GitHub Repository For implementation details, see the repository and documentation.\n","permalink":"https:\/\/jahvon.dev\/architecture\/flow\/","summary":"Flow is a local-first developer platform designed around composable YAML &ldquo;executables&rdquo;, built-in secret management, and cross-project automation.","title":"Flow"},{"content":" Info\nThis page is a public overview of a closed source project. I hope to one day open source it, but for now this is a way to share the architecture and design.\nLast updated: February 2026\nHubExt is a system that aggregates multiple smart home platforms and protocols into a single API gateway, providing unified device management, automation rules, and scene control across different ecosystems. The architecture handles real-time device synchronization, event processing, and cross-platform automation orchestration powered by flow and an eventual HubExt Dashboard.\nDesign Philosophy Unified Control Plane: Provide a single API for managing devices across multiple smart home platforms. Extensible Architecture: Support new platforms and devices through modular integration layers. Declarative Automation: Use a rules engine to define automation logic in a platform-agnostic way. System Architecture The gateway operates as a centralized control plane that bridges local smart home protocols with cloud services while maintaining responsive local control capabilities.\nTechnical Stack Go - Backend Gin API server and core logic TypeScript\/React - WIP Dashboard for device management and automation configuration Flow Powered Workflows - Declarative workflows build on top of the flow platform for observability and management via the CLI and UI Current Platform Integrations Hubitat Hub Integration\nProtocol: HTTP REST API using Makers API Authentication: Access token with App ID Communication: Local network for minimal latency Event Handling: Webhook-based real-time device updates Flair HVAC Integration\nProtocol: OAuth 2.0 REST API with automatic token refresh Components: Structures, rooms, HVAC units, sensor bridges Capabilities: Mini-split control, room temperature monitoring Weather Service Integration\nProvider: WeatherAPI.com with API key authentication Data Points: Temperature, humidity, conditions, feels-like temperature Caching: Local cache with 30-minute refresh intervals Google Calendar Integration\nProtocol: OAuth 2.0 REST API with automatic token refresh Use Case: Event-based automation triggers and conditions Pushover Notifications\nProtocol: HTTP POST requests to Pushover API Use Case: Real-time alerts for device events and automation triggers AI Integration (WIP)\nProvider: Anthropic API for natural language processing Use Case: Natural language automation rule creation and device control Device Management System Most of my devices are registered in the Hubitat platform, which provides a local API for device management. I have also been evaluating Home Assistant as a potential alternative for future integrations but have not yet migrated. The core requirements for the device management system include:\nUnified Device Model: Abstract representation of devices across platforms Capability-Based Architecture: Devices expose capabilities like switches, sensors, and thermostats Room Organization: Devices are grouped by rooms with hierarchical structure Device states are synchronized into local cache storage, providing fast API responses while maintaining eventual consistency with upstream platforms.\nAbstract Device Interface\nAll devices implement a common interface to ensure consistent interaction across platforms:\ntype Device interface { ID() string Name() string Room() room.Room MatchesName(string) bool Capabilities() []CapabilityName As(CapabilityName) Capability } This interface is implemented for each platform \/ device type once and reused across the system. It allows for flexible device management and interaction without needing to know the underlying platform details.\nCapability-Based Controls Devices expose capabilities that define their functionality, allowing for flexible control and automation. For instance:\nSwitch capabilities for on\/off control Sensor capabilities for environmental data Thermostat capabilities for HVAC control Button capabilities for trigger events type Capability interface { Name() CapabilityName IsStateful() bool CurrentState() CapabilityState Merge(Capability) SendCommand(string) error } Room Organization type Room struct { Name string `json:&#34;name,omitempty&#34;` Aliases []string `json:&#34;aliases,omitempty&#34;` Groups []Group `json:&#34;groups,omitempty&#34;` Rank uint `json:&#34;rank,omitempty&#34;` } Hierarchical room structure with groups and aliases Device-to-room mapping for contextual automation Room-based filtering and bulk operations functionality Automation Engine Event Processing The gateway now runs a fully async pipeline. HTTP ingestion returns immediately so device platforms are never blocked waiting on rule evaluation:\nHTTP ingestion returns 202 immediately (under 50ms) Deduplication window (5 seconds) prevents duplicate event processing Events are persisted to SQLite before processing \u2014 zero event loss on failure Worker pool processes events in parallel (currently 3 workers) Enrichment stage attaches device metadata, room context, and previous state Rule evaluation and action execution across relevant platforms Rules Engine Rules started as Go handlers and are moving toward declarative YAML definitions. Both coexist in the current system, with the Go approach still in place for backward compatibility.\nThe Go handler interface:\ntype Rule struct { Name string Aliases []string HandleFunc Handler } type Handler func(Event, room.List, device.List) (handled bool, err error) At the moment, all events flow through all rule handlers so they must handle their own filtering. The goal is smarter routing by device or event type, along with hot-reload so rules can be updated without a deployment.\nYAML rule definitions are now available for basic conditions and actions, with validation enforced at load time.\nScene Management Scenes follow a similar structure to rules but are predefined automation scenarios that coordinate multiple devices across platforms. Currently they&rsquo;re defined in Go:\nfunc pbChillHandler(_ room.List, curDevices device.List) (bool, error) { tableLamp := curDevices.GetDevice(registry.PrimaryBedroomTableLamp) ceilingLight := curDevices.GetDevice(registry.PrimaryBedroomCeilingLight) switch { case timeframe.CurrentDay().IsWeekday() &amp;&amp; timeframe.Night().CurTimeInFrame(): if err := tableLamp.As(device.SwitchName).SendCommand(device.OnCommand); err != nil { return false, err } if err := ceilingLight.As(device.SwitchName).SendCommand(device.OffCommand); err != nil { return false, err } return true, nil default: return false, nil } } This works but is a bit clunky. The goal is to get to declarative YAML definitions that can be created and edited without touching Go code:\nscenes: PBRChill: devices: - room: &#34;primary_bedroom&#34; type: &#34;switch&#34; action: &#34;dim_to_30&#34; - room: &#34;primary_bedroom&#34; type: &#34;hvac&#34; action: &#34;cool_to_68&#34; window: &#34;weekday night&#34; YAML scene definitions are planned alongside the dashboard UI.\n","permalink":"https:\/\/jahvon.dev\/architecture\/hubext\/","summary":"Smart Home API Gateway that provides a unified control plane for my smart home ecosystems. Providing device management, automation rules, and scene control across multiple platforms through a gateway.","title":"HubExt"},{"content":" Note\nMochi is in early access. Join the waitlist at mochiexec.io.\nMochi is a local-first DevOps dashboard built on top of Flow. Point it at a directory and it finds your development workflows and turns them into a unified, AI-enriched interface.\nDesign Philosophy Local-first: No cloud sync, no external accounts required. Your workflows never leave your machine. Built on Flow: Mochi inherits Flow&rsquo;s execution engine, secret management, and composability model rather than building a parallel system from scratch. AI-enriched, not AI-dependent: AI augments the experience with workflow analysis and suggestions, but the tool works without it. Unified discovery: Meet developers where they are instead of requiring migration to a new workflow format. System Overview 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 Flow&rsquo;s architecture. Discovery runs at startup and on sync, after which all discovered executables are available in both the dashboard and CLI identically.\nThe desktop is not yet released, but the architecture is taking shape. Here&rsquo;s a preview:\nAuto-Discovery Engine Mochi&rsquo;s discovery layer imports executables from common developer workflow files without requiring any configuration changes.\nDiscovered 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.\nThis discovery system is built on the same import infrastructure in Flow&rsquo;s core. See the Flow architecture for implementation details.\nAI Enrichment Layer 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 Ollama instance depending on your preference and privacy requirements.\nThe enrichment layer is optional and additive. The dashboard is fully functional without it, and workflow definitions are processed locally before anything is sent externally.\nDesktop and CLI Architecture 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.\nType 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 \u2014 make a change in one place and have confidence everything stays consistent.\nResources mochiexec.io Flow Architecture GitHub (flowexec org) ","permalink":"https:\/\/jahvon.dev\/architecture\/mochi\/","summary":"Mochi is a local-first DevOps dashboard that auto-discovers your workflows and turns them into a unified, AI-enriched interface. Built on Flow.","title":"Mochi"}]