<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Paul Scarrone</title>
    <description>The latest articles on DEV Community by Paul Scarrone (@paulscoder).</description>
    <link>https://dev.to/paulscoder</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F51308%2Fe9373271-b4c6-4806-af0c-d082acbc1ab7.jpeg</url>
      <title>DEV Community: Paul Scarrone</title>
      <link>https://dev.to/paulscoder</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/paulscoder"/>
    <language>en</language>
    <item>
      <title>kwike - Agent-to-Agent Orchestration in the Unix Philosophy</title>
      <dc:creator>Paul Scarrone</dc:creator>
      <pubDate>Mon, 30 Mar 2026 02:10:38 +0000</pubDate>
      <link>https://dev.to/paulscoder/kwike-agent-to-agent-orchestration-in-the-unix-philosophy-m31</link>
      <guid>https://dev.to/paulscoder/kwike-agent-to-agent-orchestration-in-the-unix-philosophy-m31</guid>
      <description>&lt;p&gt;I've been building kwike, an LLM-first tool for composing agentic workflows using Unix primitives - pipes, append-only logs, and event subscriptions instead of SDKs and harnesses.&lt;/p&gt;

&lt;p&gt;The primary use case here is agentic workflows for specific repeatable actions. What I often call drudgery. Version upgrades, dependency management, or maintaining documentation.&lt;/p&gt;

&lt;p&gt;This isn't going to run your business or make you the next 50MM in 12hr dude, but it does allow you to get some boring stuff done while you work on the stuff you like. Its a technical solution for technical problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  LLM First
&lt;/h2&gt;

&lt;p&gt;One of the features kwike focuses on is training an LLM in its use. While it has a CLI that you can use for non-agentic tooling, building a workflow is telling your LLM of choice - in my case, Claude - "You have access to kwike, an agentic workflow orchestration tool. Read &lt;code&gt;kwike --help&lt;/code&gt; and &lt;code&gt;kwike docs&lt;/code&gt;. I want to build a workflow that looks something like X. Let me know when you are ready to discuss." Much of what comes next can be constructed without any experience with the tool. It includes debugging tools and static analysis to validate configuration and the workflow DAG.&lt;/p&gt;

&lt;p&gt;While setting up a new workflow isn't immediate, rather complex behavior can be recorded and committed with your projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basics
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9x5dl84ejkmstfpv9r5l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9x5dl84ejkmstfpv9r5l.png" alt="Doc Updater"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Deep down this is an agent-to-agent communication protocol through event dispatch and subscription. The tool exposes the following primitives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dispatch (emit events to an event store)&lt;/li&gt;
&lt;li&gt;consume (pull events subscribed to)&lt;/li&gt;
&lt;li&gt;daemon (event store owner, can mesh across network boundaries)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The consumer watches for one or more messages defining its own partition and maintains its own read cursor. The daemon in this case acts as a mail server, essentially, and new messages are dispatched with the intent of being reactive in nature. Think of it as allowing system events or another LLM's tool use to spawn a dedicated workflow.&lt;/p&gt;

&lt;p&gt;Each workflow step is called a uniform and is assigned to a dedicated consumer process. A uniform is a prompt template passed to the underlying tool the consumer executes. The goal here is to force the LLM to consume and produce a consistent contract so agents and scripts can exchange data. This protocol also provides some guarantee that agentic processes complete their work by requiring a JSON schema which is used to validate the agent output.&lt;/p&gt;

&lt;p&gt;In many cases this becomes a context exchange between agents. Reminding working directories or fan-out/fan-in statuses. This context exchange is generally analogous to an email thread. A consumer can subscribe to many sources and can for claude-code force a clean context or continue a session&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
  &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task.implement"&lt;/span&gt; &lt;span class="c1"&gt;# subscribed events &lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task.review.rejected"&lt;/span&gt; 
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task.review.approved"&lt;/span&gt; 

&lt;span class="na"&gt;session&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
  &lt;span class="na"&gt;fresh_types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;task.implement&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# these create new sessions &lt;/span&gt;
  &lt;span class="na"&gt;resume_types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;task.review.rejected&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# these resume existing sessions &lt;/span&gt;
 &lt;span class="c1"&gt;# task.review.approved is neither → defaults to fresh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each tool call or agent can map lifecycle events so the output contract can inform on &lt;code&gt;.done&lt;/code&gt; and &lt;code&gt;.failed&lt;/code&gt;. When these mappings exist a successful tool or agent completion results in a new event dispatched as a threaded reply. Allowing other consumers to participate in the work with their own uniform defining a task.&lt;/p&gt;

&lt;h2&gt;
  
  
  Claude-Code Caveat
&lt;/h2&gt;

&lt;p&gt;Much of this has been built around claude-code and there are some limitations towards other agents right now but this is what I have so far. Claude for example as the tool supports session resume which permits retries and replies to maintain a conversationally bound session to speed things up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Couple Line Example
&lt;/h2&gt;

&lt;p&gt;Here is an example of "watch" which is a convenience function to auto "dispatch" from any command output on an interval. Requires a running kwike daemon.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# watch a git repo, dispatch changes, consume with an agent&lt;/span&gt;
kwike watch &lt;span class="s2"&gt;"git diff --stat HEAD~1"&lt;/span&gt; &lt;span class="nt"&gt;--type&lt;/span&gt; repo.change &lt;span class="nt"&gt;--interval&lt;/span&gt; 60s
kwike consume &lt;span class="nt"&gt;--config&lt;/span&gt; ./agents/writer/consumer.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anyway I have plenty of examples in the repo, &lt;a href="https://git.sr.ht/~ninjapanzer/kwike" rel="noopener noreferrer"&gt;https://git.sr.ht/~ninjapanzer/kwike&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is plenty left to do but I would love to hear some feedback on the architecture and assumptions around contract constraints as a mode to make LLMs behave. The crash-only approach, and the message durability that mirrors Kafka but acts more like a newsgroup server and mail clients.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>tooling</category>
      <category>automation</category>
    </item>
    <item>
      <title>Automatic Programming: Iteration 4</title>
      <dc:creator>Paul Scarrone</dc:creator>
      <pubDate>Sun, 08 Feb 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/paulscoder/automatic-programming-iteration-4-1ce6</link>
      <guid>https://dev.to/paulscoder/automatic-programming-iteration-4-1ce6</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevelopmeh.com%2Fdevex%2Fmark1-computer.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevelopmeh.com%2Fdevex%2Fmark1-computer.jpg" alt="Grace Hopper: Mark 1 Computer" width="1185" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Automatic Programming: Iteration 4 🔗
&lt;/h2&gt;

&lt;p&gt;While I know that the discourse is not complete binary whether you are for or against LLM generated code it's probably the right time to take a step back a few years and explore the iterations of our industry.&lt;/p&gt;

&lt;p&gt;Let's just work backwards, COBOL, iteration 3.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="//Synonymous%20with%20evil."&gt;Common Business-Oriented Language&lt;/a&gt; A weak, verbose, and flabby language used by code grinders to do boring mindless things on dinosaur mainframes. Hackers believe that all COBOL programmers are suits or code grinders, and no self-respecting hacker will ever admit to having learned the language. Its very name is seldom uttered without ritual expressions of disgust or horror.&lt;em&gt;Evans, Claire L - Broad band&lt;/em&gt; the untold story of the women who made the Internet_ -&amp;gt; &lt;em&gt;From The Hacker's Dictionary&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Yes, the perpetual software of big finance and numerous other systems. While I haven't written COBOL myself I have had to rewrite at least one system written in COBOL.&lt;/p&gt;

&lt;p&gt;Poking fun aside COBOL exists because of the dream that non-experts could be computer programmers. By today's standards you still need to be an expert to write COBOL. Prior to its creation, software was written in assembly and before that machine code, and before that patch cables, actual wires, iteration 1.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Grace knew that would only happen when two things occurred:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Users could command their own computers in natural language.&lt;/li&gt;
&lt;li&gt;That language was machine independent. That is to say, when a piece of software could be understood by a programmer as well as by its users, when the same piece of software could run on a UNIVAC as easily as on an IBM machine, code could begin to bend to the wills of the world. Grace called this general idea "automatic programming"...&lt;em&gt;Evans, Claire L - Broad band&lt;/em&gt; the untold story of the women who made the Internet_&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;That Grace of course was Grace Hopper, and she was obsessed with making programming easier and more efficient. In her time programming was a kind of wizardry and very few knew the incantations to make the computer operate. From a business standpoint making programming easier was bad for business since computer companies sold the computer and the software.&lt;/p&gt;

&lt;p&gt;Portable programs, ones that could be written on any machine for any other machine was a business risk. It created competition so there was resistance.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Those who resisted automatic programming became known as "Neanderthals." They might as well have called themselves framebreakers, as Lord Byron had over a century before.&lt;em&gt;Evans, Claire L - Broad band&lt;/em&gt; the untold story of the women who made the Internet_&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;"Framebreakers" refers to those workers who opposed the automatic loom, better known as the Luddites.&lt;/p&gt;

&lt;p&gt;Before computers cloth was made on the loom and the origin of the punchcard was used to create an automatic loom. After its invention there was not much use for using a manual loom. It was disruptive and changed an entire industry, displacing workers.&lt;/p&gt;

&lt;p&gt;Grace and her cadre believed in a future where the programs write themselves. There is some corollary to today with code generation, which actually is programs writing themselves, something Grace dreamed of. The difference between the loom and the compiler is only in the growth potential. Cloth was an end result of a chain of optimization but while you could tirelessly create it in any pattern imaginable, someone still needed to imagine the patterns. Variety of cloth became commonplace, the art remained, the drudgery was lost.&lt;/p&gt;

&lt;p&gt;Now I respect that for some the act was the value, I share those feelings. I love writing the actual code, I care about it more than the product it produces. An opinion whose popularity depends on what side of the invoice you sit.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A quick lesson: computers do not understand English, French, Mandarin Chinese, or any human language. Only Machine code, usually binary, can command a computer, at its most elemental level, to pulse electricity through its interconnected logic gates.&lt;em&gt;Evans, Claire L - Broad band&lt;/em&gt; the untold story of the women who made the Internet_&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Programs are essentially the aggregation of basic operations, layers upon layers. If we think of code generation as just another kind of compiler it's part of a long lifeline of change approaching the ideal "automatic programming."&lt;/p&gt;

&lt;p&gt;Of course I would prefer to ask Grace her formal opinion. But in her time when presenting her arguments, mathematicians were once inundated in the tedium of arithmetic to solve their equations. Computers arrived and essentially removed the need for those steps and allowed them to get closer to the interesting part, the solutions. She argued that the compiler did the same thing, modulating the complexity of using computers allowed programmers to spend more time on stimulating thoughts.&lt;/p&gt;

&lt;p&gt;Of course the reality was mathematicians became programmers to advance their work.&lt;/p&gt;

&lt;p&gt;Programmers used compilers to build elegant languages, and COBOL.&lt;/p&gt;

&lt;p&gt;I think it's quite funny to have the perspective that programs are binary, iteration 2, and writing the program for the computer was to create something that could accurately generate binary programs.&lt;/p&gt;

&lt;p&gt;Now we stand at another transition where automatic programming is telling the computer to write the code that the compiler turns into a binary program. If the compiler was the 3rd level operation we are now at the 4th.&lt;/p&gt;

&lt;h2&gt;
  
  
  Here is where it all came together though: 🔗
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Grace loved coding, but she admitted that "the novelty of inventing programs wears off and degenerates into the dull labor of writing and checking programs. This duty now looms as an imposition on the human brain"&lt;em&gt;Evans, Claire L - Broad band&lt;/em&gt; the untold story of the women who made the Internet_&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I have been feeling this for years, the code just keeps getting more repetitive. I just keep doing the same extremely complicated and extremely boring operations over and over again. The novelty of software I grew up with in the 2000s is over. Everything is a framework or a dogma and all solutions are solving the same problems with a different color scheme and font.&lt;/p&gt;

&lt;p&gt;If anything would make me embrace the 4th level it's this, even if it means no one needs me anymore. I can at least see the realization of Grace's dream and in some way if everyone becomes a programmer finally, I'll have more people to talk to about what I love.&lt;/p&gt;

&lt;p&gt;We aren't there yet, the software world is still pretty complicated and you have to know a lot of special dance moves to get things working right. But it's not going to be forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  The book 🔗
&lt;/h2&gt;

&lt;p&gt;Before I wander off into a diatribe of where our future is going lemme just stop and tell you to read this book:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.penguinrandomhouse.com/books/545427/broad-band-by-claire-l-evans/" rel="noopener noreferrer"&gt;Broad Band - Claire L. Evans&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's a good one, it has changed my mind on if I will continue to call myself an engineer or a programmer. The definition has finally been clarified. I was today - 2 weeks old and that was too long to know the truth.&lt;/p&gt;

&lt;p&gt;Also it answers the question of why women were not present in Computer Science but I was taught by women who had careers in computer science.&lt;/p&gt;

&lt;p&gt;Point is strong recommendation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where are we going? 🔗
&lt;/h2&gt;

&lt;p&gt;I dunno, maybe we are all out of work. Maybe our MBA Degree bosses will finally see their reality of the numbers going up and to the right forever.&lt;/p&gt;

&lt;p&gt;I see it like this, programming ended when the job absorbed all its roles by mere definition. It has been an amalgam for a while and that has been a crime. Now I can focus on building things again at the scale required in our times. I can concurrently build 2 or 3 projects while focusing on my writing. Sounds like a dream and the troubles of today are not forever.&lt;/p&gt;

&lt;p&gt;The current goals of centralized AI is unsustainable and within a few years the ASICs will arrive, our computers will be packed with high bandwidth memory and the models will be local. Just like how all of a sudden we all started walking around super computers in our pockets we will build the infrastructure to build all the hardware we need to move forward.&lt;/p&gt;

&lt;p&gt;I mean we live in the dumbest timeline and greed seems to be winning but if the pattern from past is here to loop again we go from the dumb time to the bright time for a while again. I am looking forward to that at least.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>computerscience</category>
      <category>programming</category>
    </item>
    <item>
      <title>Keep Your Eyes on the IDE, and Your Robots on the Tickets</title>
      <dc:creator>Paul Scarrone</dc:creator>
      <pubDate>Sun, 08 Feb 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/paulscoder/keep-your-eyes-on-the-ide-and-your-robots-on-the-tickets-4hac</link>
      <guid>https://dev.to/paulscoder/keep-your-eyes-on-the-ide-and-your-robots-on-the-tickets-4hac</guid>
      <description>&lt;h2&gt;
  
  
  Keep Your Eyes on the IDE, and Your Robots on the Tickets 🔗
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Initial Scene:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Narrator: Bead Manager?! What does that even mean... let's start back at the beginning:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Scene Break:&lt;/em&gt; (Dissolve)&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Time Jump:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;"Two weeks earlier..."&lt;/p&gt;

&lt;p&gt;&lt;em&gt;New Scene:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A tall handsome man with thick dark hair leans over a computer with boxes of black bordered in grey scrolling dark green text. Scowling...&lt;/p&gt;

&lt;p&gt;Author enters the room&lt;/p&gt;

&lt;p&gt;Author: Who the hell are you! Get away from my laptop! Freaking coffee shops...&lt;/p&gt;

&lt;h3&gt;
  
  
  The Hero's Journey 🔗
&lt;/h3&gt;

&lt;p&gt;As you can imagine I have been following the post transformer LLM growth for about 4-5 years at this point. I didn't understand it and I never really used it but I keep my ear to the ground. Increasingly frustrated with the inability to keep the LLM on task. I mean its ignorance on my part and the tool isn't ready yet. Such is the mark of progress, things improve over time. Although I am still challenged with simple things.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Give me 20 variations of this prompt for as jsonl training data using X format&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I get 8...&lt;/p&gt;

&lt;p&gt;I get 23&lt;/p&gt;

&lt;p&gt;I get 12&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Jump Cut:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Laptop launches out the window&lt;/p&gt;

&lt;p&gt;So that's problem one and how do we solve it? Well with a novel wrapper that counts outputs and then re-prompts to do it again. I think they call that the &lt;em&gt;Ralph Loop&lt;/em&gt;, I don't, I just call it the nature of the thing.&lt;/p&gt;

&lt;p&gt;I learned later that this is generally caused by ambiguity of the context. Asking for 1 item 20 times and feeding back in the previous set to avoid duplicates always works better. The teaching: the computer is dumb, don't make it think too hard and everything goes smoother.&lt;/p&gt;

&lt;p&gt;Most of what is to follow is the application of &lt;a href="https://dev.to/soft-wares/agentic-patterns-elements-of-reusable-context-oriented-determinism/"&gt;Agentic Patterns: Elements of Reusable Context-Oriented Determinism&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Beads 🔗
&lt;/h3&gt;

&lt;p&gt;What beads provides is really just an idea and its worth exploring yourself: &lt;a href="https://github.com/steveyegge/beads" rel="noopener noreferrer"&gt;https://github.com/steveyegge/beads&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It self describes as "A memory upgrade for your coding agent," which I think is arguable but it was the trigger I needed to expand my concept of what a workflow with an LLM could look like. To be honest I didn't just go "Ah Beads! Its all clear now." Instead I found this article about &lt;a href="https://steve-yegge.medium.com/welcome-to-gas-town-4f25ee16dd04" rel="noopener noreferrer"&gt;Gas Town&lt;/a&gt; which I didn't read, thanks ADHD, and instead installed it blindly. If I was to give it a review it would be that Gas Town is kind of a meme of agent orchestration. Clearly, there is a lot of work put into it, but I think the author might agree that its an expression of an idea in a more artistic than practical form.&lt;/p&gt;

&lt;p&gt;But who cares, I walked back from Gas Town to beads, the underlying magic, in my opinion. So I describe this as a context graph, I am able to manually or with an agent LLM extract just as much focused context as I want and use it as a concrete repeatable prompt. While the same prompt doesn't get the exact response each time, the same prompt gets generally the same tool use and generally the same code is constructed. Which makes me wonder if the variability of code is so limited by its grammar restrictions that LLMs have less predictive options to bias towards.&lt;/p&gt;

&lt;p&gt;Ok, I am gilding the lily a bit, a bead is just a bug ticket or a todo list and its a prompt that has a dependency chain. How I am using it is more like Jira for robots, if Jira wasn't software designed for my suffering. I am able to build a feature and break down tasks then feed a path of those tasks to the agent.&lt;/p&gt;

&lt;p&gt;You may be asking, but why not just use markdown files or JSONL. Well because I am a human and I hate reading JSONL files, I have ADHD so if the file is longer than 10 lines it will never be fully read. Better put what you want as the last line on the bottom cause thats all I see. Point is I need to be able to monitor, tune, and track the agents. See what Gas Town did was have the agents self-manage. While novel, its a bit bizarre when you are trying to avoid scope creep cause LLMs love to add features.&lt;/p&gt;

&lt;p&gt;Back to the other question, why not markdown files. Well, two reasons, first they are kinda noisy, second if the LLM has to read more than the exact section of the file they are working on some ambiguity could be introduced. If you notice the agent will often scan a file 50 lines at a time if there is no index. Which means some of that ends up in its context. When we want determinism our first goal is to make sure each interaction is exactly the same prompt. This means beads is mostly an opinion and is probably not required.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stay in the IDE and Manage your robots 🔗
&lt;/h3&gt;

&lt;p&gt;So good choices after bad maybe but when I have a database for my tasks and their prompts I need a way to visualize it. The purpose here is to allow me to create and observe the tasks my agent orchestration is running on. For me this is just Claude Opus delegating tasks to Sonnet agents in an agentic loop.&lt;/p&gt;

&lt;p&gt;This all started with this command &lt;code&gt;bd graph --compact --all&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxpk40uc0gtkb2p1cjkuv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxpk40uc0gtkb2p1cjkuv.png" alt="Beads graph output" width="800" height="642"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All because I wanted to watch my agent orchestration work through my tickets for another project.&lt;/p&gt;

&lt;p&gt;Well that has led to this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnubnfo1havc7fevts46x.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnubnfo1havc7fevts46x.gif" alt="Beads Manager plugin demo" width="560" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A full management console that lets me watch the beads transition status but also let me edit and add comments.&lt;/p&gt;

&lt;p&gt;In this video there is an experimental refinement mechanism being demonstrated, available in the current release: &lt;a href="https://plugins.jetbrains.com/plugin/30089-beads-manager" rel="noopener noreferrer"&gt;Jetbrains Marketplace&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The workflow 🔗
&lt;/h3&gt;

&lt;p&gt;So the other half of this tool is this set of prompts for claude: &lt;a href="https://github.com/ninjapanzer/beads-orchestration-claude" rel="noopener noreferrer"&gt;beads-orchestration-claude&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now this is for claude but the practice can be applied manually or using other agents, the pattern is what matters and the prompt helps encapsulate the pattern more than the agent.&lt;/p&gt;

&lt;p&gt;The keys here are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recoverable&lt;/li&gt;
&lt;li&gt;Durable&lt;/li&gt;
&lt;li&gt;Keep your eyes in the IDE&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  1. Planning 🔗
&lt;/h4&gt;

&lt;p&gt;So our first path here is to plan out a feature. This is really the only time we have a discussion with the LLM but my recommendation is to write a brief in a markdown file. A musing is good enough where you describe the problem, some technical planning around constraints and the systems you want to support.&lt;/p&gt;

&lt;p&gt;What you do for any brief, use-cases, goals, non-goals, definitions, and open questions.&lt;/p&gt;

&lt;p&gt;Once this is prepared you hand this over to the agent. For me that uses the &lt;code&gt;/new project&lt;/code&gt; command &lt;a href="https://github.com/ninjapanzer/beads-orchestration-claude?tab=readme-ov-file#new-project-setup" rel="noopener noreferrer"&gt;REF&lt;/a&gt; if we provide it with &lt;code&gt;project name&lt;/code&gt; &lt;code&gt;readme&lt;/code&gt; &lt;code&gt;git remote url&lt;/code&gt; it will setup a baseline project with beads using some LLM magic and a bash script read the brief and prepare the project with a proper explanation of the project for CLAUDE.&lt;/p&gt;

&lt;p&gt;Once we have a nice agent specific write up for the project, which is important, we can begin planning. Beads provides some tools that will naturally be injected into your project to help the agent. But you may need to tell your agent this&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;use beads &lt;code&gt;bd&lt;/code&gt; to plan out tasks for this project, &lt;code&gt;bd prime&lt;/code&gt; for an overview of commands&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;bd prime&lt;/code&gt; exposes an agent friendly output for how to invoke commands.&lt;/p&gt;

&lt;p&gt;Your agent should now be creating issues in beads for your project. Depending on how you like it you can use as many or as few features as you like from beads, which has a number of fields to hold context about actions. At the very simplest you will get titles and descriptions. If you asked for a feature or an epic you will find they may have been mapped as dependencies.&lt;/p&gt;

&lt;p&gt;You should then review the tasks. This can be done with &lt;code&gt;bd list&lt;/code&gt; and &lt;code&gt;bd show id&lt;/code&gt; or use the jetbrains plugin.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Review 🔗
&lt;/h4&gt;

&lt;p&gt;So now we review the beads and expand / contract the plan asking the agent to defer tickets we are unsure about or expand others.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Work Breakdown 🔗
&lt;/h4&gt;

&lt;p&gt;This is probably the most important part. Ask a reasoning model to review all the beads and provide implementation details for those exact tasks in the beads. The idea here is to have the agent make a big plan but instead of writing all the code write code snippets that are attached to the tasks.&lt;/p&gt;

&lt;p&gt;We can then take the vibe code approach and execute on this or do a pre-review of our code. Its not uncommon for the agent to have wandered down a bad architecture path. Here is our moment to focus on a specific task and a specific ticket and allow things to be revised in a focused way.&lt;/p&gt;

&lt;p&gt;The best way to do this is to first clear your context and ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Given the project overview please review bead  and revise to include an a single refresh flow for all data sources. Also review implementation details.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  4. SDLC 🔗
&lt;/h4&gt;

&lt;p&gt;Tell the agent to now make documentation and testing tasks linking them as required to the beads that relate to them. You should end up with a layer 2 of tasks that will follow up after the implementation completes.&lt;/p&gt;

&lt;p&gt;I usually then ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Given the use-cases in the project overview define an e2e testing ticket for planning e2e tests that we can review at the end.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If all is well the agent should create a task that it will stop and design testing with you that include acceptance criteria based on the provided use-cases.&lt;/p&gt;

&lt;h4&gt;
  
  
  5. Burn tokens 🔗
&lt;/h4&gt;

&lt;p&gt;Now we get to the more technical part. We need to delegate actions to sub agents and depending on what agent infra you use this could be built-in or require manual orchestration.&lt;/p&gt;

&lt;p&gt;The command &lt;code&gt;/beads-orchestrate&lt;/code&gt; &lt;a href="https://github.com/ninjapanzer/beads-orchestration-claude/tree/master?tab=readme-ov-file#beads-orchestration" rel="noopener noreferrer"&gt;REF&lt;/a&gt; handles most of the heavy lifting.&lt;/p&gt;

&lt;p&gt;It instructs the orchestrator to fork new processes using a template. For claude this means it will append&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--dangerously-skip-permissions --model sonnet|haiku --print -p "..."

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the prompt it will read the bead details along with some workflow about updating the bead and write that new prompt to a temp file. Passing the temp file to the new process. This obviously gives you the ability to debug what is happening at the injected prompt level.&lt;/p&gt;

&lt;p&gt;It then sleeps and waits for the sub process to finish.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Why?&lt;/em&gt; Well Claude is just a nodejs app and it eventually runs out heap space because it reads the stdout and stderr of all tasks it orchestrates internally. As a subprocess it watches, it's a fresh Claude instance so if it fails it fails in a recoverable way. Since the prompt file is named after the bead it can recover by just restarting the agent.&lt;/p&gt;

&lt;p&gt;At this point the orchestrator should spawn the implementer which reads the implementation details and completes the work.&lt;/p&gt;

&lt;p&gt;Then the orchestrator will spawn a new agent to handle code review, usually a simpler agent.&lt;/p&gt;

&lt;p&gt;All this time the agents will leave comments on the tickets so you can see where it ran into problems and picked a tradeoff. This is very important for root cause analysis later if a feature degrades. You can have the LLM resurrect the changes merged into a branch the same name as the bead. Review the decision it made and make another one. Better yet, since the orchestrator and implementer read the comments you can just append your request to the ticket, mark it open, and tell the agent to refactor it again.&lt;/p&gt;

&lt;p&gt;Here is an example of what a bead comment thread looks like in practice. This is from an issue where the Gradle build was failing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;[Opus]&lt;/strong&gt; at 2026-02-06 01:11 Starting orchestration. Dispatching Sonnet implementer to fix Kotlin stdlib warning in gradle buildPlugin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Opus]&lt;/strong&gt; at 2026-02-06 01:16 Resuming orchestration. Dispatching Sonnet implementer to fix Kotlin stdlib warning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Opus]&lt;/strong&gt; at 2026-02-06 01:24 Resuming orchestration. Previous worktree had no commits - starting fresh. Dispatching Sonnet implementer to fix Kotlin stdlib warning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Sonnet]&lt;/strong&gt; at 2026-02-06 01:25 Starting implementation. Will examine build.gradle.kts and gradle.properties to understand current configuration, then apply fix per &lt;a href="https://jb.gg/intellij-platform-kotlin-stdlib" rel="noopener noreferrer"&gt;https://jb.gg/intellij-platform-kotlin-stdlib&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Sonnet]&lt;/strong&gt; at 2026-02-06 01:26 COMPLETED: Added kotlin.stdlib.default.dependency=false to gradle.properties. Build verified successful without warnings. Fix committed to branch.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Notice how the orchestrator (Opus) had to resume twice - once after the first dispatch seemingly stalled, and again when it found the worktree had no commits. This is the kind of recovery that happens automatically. The implementer (Sonnet) then picked up the task, did its research, applied the fix, and verified success. All of this is visible in the ticket history without watching terminal output scroll by.&lt;/p&gt;

&lt;h4&gt;
  
  
  6. When it fails 🔗
&lt;/h4&gt;

&lt;p&gt;This workflow isn't perfect but thats the big reason for the plugin. This whole process keeps you from staring at the chat stream and back into the IDE as your work. If you see progress not being made or an issue has comments that move it to blocked you can address it there and then just kick the orchestration. The goal is we have boring work we don't wanna do and we let the robot do it while we act on the interesting parts.&lt;/p&gt;

&lt;p&gt;But sometimes it just hangs, haven't solved it yet. When this happens we are always recoverable. Claude subprompts have a 10 minute timeout so even orphaned they will be killed. You just start orchestration again on a clear context and things recover without your attention.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>productivity</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Agentic Patterns: Elements of Reusable Context-Oriented Determinism</title>
      <dc:creator>Paul Scarrone</dc:creator>
      <pubDate>Fri, 06 Feb 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/paulscoder/agentic-patterns-elements-of-reusable-context-oriented-determinism-43k7</link>
      <guid>https://dev.to/paulscoder/agentic-patterns-elements-of-reusable-context-oriented-determinism-43k7</guid>
      <description>&lt;h2&gt;
  
  
  Agentic Patterns: Elements of Reusable Context-Oriented Determinism 🔗
&lt;/h2&gt;

&lt;p&gt;While not as exhaustive as the title might indicate but aligned with my focus on enforcing as much determinism as possible from any given LLM ala Article let's take a look at exploiting tool using LLMs as a process instead of as a conversation. As I posed in the linked article much of the failures we experience are related to attention and confusion which is the progressive noise we introduce as we try to convince the model to perform an action.&lt;/p&gt;

&lt;p&gt;What I describe below are patterns for building &lt;a href="https://developmeh.com/tech-dives/a-deterministic-box-for-non-deterministic-engines/" rel="noopener noreferrer"&gt;A Deterministic Box for Non-Deterministic Engines&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Chats are an artifact 🔗
&lt;/h3&gt;

&lt;p&gt;This behavior of progressing the chat with multiple statements to a solution is merely an artifact of pre-tooluse models. So we the humans needed to interact with moving files and integrating code at each step while testing it became natural to turn interactions into long conversations. Ones that eventually degrade into failure loops, while surely someone has told you to just keep clearing your context and start over.&lt;/p&gt;

&lt;p&gt;Since the evolution of tools like functiongemma which provides trainable, simple function calling on commodity hardware we are on the edge of building decision trees for tool oriented expert systems, but that's a topic for a different day. For now the models we have that are effective tool users are too large to be portable and our contract is still text.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reduction in variability 🔗
&lt;/h3&gt;

&lt;p&gt;You may recall from math class that you should avoid deriving new values from derived values until you can prove the quality of the procedure. As any instability in accuracy will grow the inaccuracy of outputs. The same is essentially the normal behavior of long running chats. Since model responses can essentially steer (influence) the decisions of the model in the future of the same context window we can fall into a quality trap.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import anthropic

client = anthropic.Anthropic()
messages = []

while True:
    # Get input from you
    user_input = input("You: ")

    # Put your message in the context
    messages.append({"role": "user", "content": user_input})

    # Send the context to the LLM
    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=1024,
        messages=messages, # Full history sent each time
    )

    # LLM response
    assistant_message = response.content[0].text

    # LLM response added to context
    messages.append({"role": "assistant", "content": assistant_message})

    print(f"Claude: {assistant_message}\n")

    # Loop

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As this partial example shows if we send 3 messages there will be 3 responses and our context is 6 messages. Each time we send something new the LLM rereads the entire context not just the last message meaning any derived issues that we or the LLMs predictive invariability adds can pollute the quality of the overall decisions made. There are also unseen patterns due to how training is compressed that leads to non-intuitive work and concept replacement for convoluted examples. For example the LLM will be more accurate if there was more reinforcement during deep learning of that topic and it fails faster when exercising in novel space. If you wanna understand this better go read this book: &lt;a href="https://sebastianraschka.com/llms-from-scratch/" rel="noopener noreferrer"&gt;https://sebastianraschka.com/llms-from-scratch/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  KISS the LLM 🔗
&lt;/h2&gt;

&lt;p&gt;So the solution is the same as it ever was. Keep things small and focused on a single task. The LLM isn't a person and doesn't think, we are using human language to steer outputs the same way we write function signatures to have enough context to supply information to downstream operations. That doesn't mean we never chat with the bot. It does have a big context window and we can take advantage of that for specific patterns. Plan-Then-Execute Pattern&lt;/p&gt;

&lt;p&gt;As discussed we want to keep context focused when we need a long running session. This is the key to plan-then-execute. Coding agents' system prompts have been biased towards creating implementations which like an eager intern jumps the gun and starts building before understanding. When this happens we find ourselves immediately refactoring the wrong idea. The context becomes polluted with examples of the wrong solution and leads to lower quality outputs.&lt;/p&gt;

&lt;p&gt;While some coding agents have a "planning" mode, this is a system prompt hack to try and keep it from producing but I'll admit I have had lower luck because this funnels you towards implementation faster. The solution here is to work with the agent's bias to produce and have it produce research artifacts. It will gladly deep dive into a code-base and provide elegant descriptions of architecture and sequence. This is best performed with a reasoning model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kill-Then-Breakdown Pattern 🔗
&lt;/h2&gt;

&lt;p&gt;A sub-step of plan then execute requires context flushing. After we have verified the quality of the research we start fresh and have the next agent, preferably a reasoning model, read that research document and we instruct it to break down the work into tasks and provide a planned implementation to each task. Once again we are working towards the models goals of writing code or producing files and we get small snippets of code associated with each task. The plan and breakdown is a token heavy portion of the work stream but since we keep check-pointing with artifacts written in markdown there is a repeatable retention in value. That said context size does play a part in cost so flushing the context and loading a compacted version of the topic does end up saving some cost over the context exploding and the risk of loss during compaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Now Execute (Mr. Meeseeks Pattern) 🔗
&lt;/h2&gt;

&lt;p&gt;While you can use a single reasoning model to go through each task it has broken down and implement it there is a better way. We can ask the reasoning model to act as an orchestrator that spawns sub-agents of cheaper models for each task in the Mr. Meeseeks pattern. The reasoning model starts a simpler model and passes it the task and expected implementation we just broke down and goes to work on it. For simplicity's sake don't run these operations in parallel yet without some considerations to keep multiple agents from overwriting themselves. As each task is marked completed the sub-agent will be killed and a new one with a fresh context is started.&lt;/p&gt;

&lt;p&gt;It's important to remember that the orchestrator gets the output from the subagents so if your development environment produces a lot of noise or if your agents aren't clamping their read size you may run into interesting scenarios where you overflow the coding agents memory. The solution here is to run each sub-agent in a new process instead as a task run by the first coding agent. I am sure you can see how this can expand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Specification-Driven Agent Development Pattern 🔗
&lt;/h2&gt;

&lt;p&gt;Is what we just accomplished. During planning we created a specification from an existing code-base or a set of discussions. Then we captured focused implementation details. Then we did a bunch of tiny implementations. While the more formal nature of spec driven usually stops at the original manifest of "what is this feature going to be" we should take it one step further to actually storing partial facts about implementation. On the consumption side of the coder agent it will be somewhat literal with what it was given but it has to perform integration and resolve writing tests as well as ensure the work fits into existing tests and functionality.&lt;/p&gt;

&lt;p&gt;Also given we have this spec we can add extra steps to our workflow. The orchestrator ends up following a very simple workflow and its focus is retained around the same document of compressed knowledge it wrote. This is an important fact because models are specifically expressive, they talk a certain way, which means a model reading what it wrote is less ambiguous than it reading what you wrote. It has enforced patterns from training we can reactivate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agent Verifier Pattern (Code Review) 🔗
&lt;/h2&gt;

&lt;p&gt;Since we have all these concrete artifacts regarding code and spec and final implementation we can then as our last step ask a small simple agent to just give us a thumbs up or down on it, essentially a code reviewer. Before we determine something is done we allow a new context to observe just the changes and the spec, if it rejects we spawn a new implementer to try again. Then we spawn a new reviewer and review again until it works.&lt;/p&gt;

&lt;p&gt;In practice this interaction looks a little something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb1oei1eze946o3oxanzy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb1oei1eze946o3oxanzy.png" alt="Agent verifier pattern in practice" width="800" height="297"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;During the end of this task the image to be added wasn't correct and the reviewer failed it causing it to loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prompt Writer Agent Pattern 🔗
&lt;/h2&gt;

&lt;p&gt;So this doesn't work out of the box but it's pretty easy mock up. The next step is to codify what we send during each phase of execution. For this to work we need to be very explicit. Even though the orchestrator knows the workflow it may forget as it handles agent spawning which leads to the workflow rules not being transferred to the sub agents. We are in a derived value degradation problem again.&lt;/p&gt;

&lt;p&gt;We have to help the orchestrator by providing it a template of the actions we want each sub-agent to take. It can fill in the gaps with the task. So before it spawns an agent it reviews the workflow and writes the sub-agent prompt to a file. It then tells the sub-agent to read the file and implement. This provides two benefits to accuracy. Since the orchestrating agent has to keep re-reading the template ala RE2 (Read and Re-read prompting) it retains more attention because it keeps getting repeated in the context. Since it then writes the refined prompt for the agent if we crash or context collapses we can immediately recover by reviewing overall task process and the presence of the prompt files. It is in fact highly durable allowing multiple orchestration concurrently if you have the money.&lt;/p&gt;

&lt;p&gt;Additionally, the reviewer will get its own prompt written but it can review the coders prompt when checking for spec compliance.&lt;/p&gt;

&lt;h3&gt;
  
  
  In Practice 🔗
&lt;/h3&gt;

&lt;p&gt;If I align this to Anthropic models:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Orchestrator -&amp;gt; Opus (Reasoning)&lt;/li&gt;
&lt;li&gt;Implementer -&amp;gt; Sonnet (Competent)&lt;/li&gt;
&lt;li&gt;Reviewer -&amp;gt; Haiku (Simple)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also don't rely as much on markdown files past the very first phase of planning. I move all context with the exception of sub agent prompts into a graph. For that graph I use beads which while it has its flaws enables an approach I call the "Context Graph Pattern" which I will go into in a bit.&lt;/p&gt;

&lt;p&gt;What beads essentially is is Jira or Linear but with a outputs that work better for LLMs. Essentially a command line tool that has a help dialog that outputs markdown instructions, which improves comprehension by the LLM. It's a graph because like any issue tracker issues can form chains and comments.&lt;/p&gt;

&lt;p&gt;In the previous picture above this comment stream is from a plugin to interact with my graph visually. It permits me to leave comments for the agents or even rewrite a spec on the fly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context Graph Pattern 🔗
&lt;/h2&gt;

&lt;p&gt;Using a tool that allows me to commit context as a focused structure means I get reproducibility and an audit log. Since beads uses issue IDs as commit names the graph extends into the git history. Code and spec and decision tree can all be one artifact without reading all the files. This keeps our context as tight as possible.&lt;/p&gt;

&lt;p&gt;Because the graph is mutable if the first attempt was a complete failure I have two choices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provide feedback as a refinement and retry -&amp;gt; Refactor&lt;/li&gt;
&lt;li&gt;Rewrite the spec and have the agent pull the previous changes and start over -&amp;gt; Rewrite&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I can continue to iterate this way at a much lower time cost to me as a developer and since the graph is also able to be committed to a repo and shared with other developers they can do the same.&lt;/p&gt;

&lt;p&gt;When we enhance a feature we can include the previous changes either by diff review and spec retrieval from the graph or by explicit linking within the graph itself. There is a portion of this structure that lets you act as Product, Project, and Tech Lead for the given outcomes.&lt;/p&gt;

&lt;p&gt;Of course no silver bullet, you will end up a developer for some things in the end don't worry. But this can be guided by a concrete context for yourself when you ask the LLM what it thinks went wrong and you get your hands dirty.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 🔗
&lt;/h2&gt;

&lt;p&gt;If you wanna see a functional example of this process I have been dog-fooding it for a bit and all the artifacts from the plugin I posted a picture of are over here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://git.sr.ht/%7Eninjapanzer/jetbrains-beads-manager" rel="noopener noreferrer"&gt;https://git.sr.ht/~ninjapanzer/jetbrains-beads-manager&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Install: &lt;a href="https://plugins.jetbrains.com/plugin/30089-beads-manager" rel="noopener noreferrer"&gt;https://plugins.jetbrains.com/plugin/30089-beads-manager&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A majority of this code was written in my absence in a execution loop. This usually gets you about 80% there. I then spend some time filing bug tickets adding clarifications and refinements. There were only 3 actual chat sessions that occurred during integration where I provided some focused behavioral examples and some bulk documentation where it built some new tasks and orchestrated them.&lt;/p&gt;

&lt;p&gt;I would call this a mature alpha as it was produced in 1 sitting. Functionality is usable enough that I finished the development only using the plugin. But this isn't showing off If you pull this down and have beads installed you can see my prompts and what an actual context graph looks like.&lt;/p&gt;

&lt;h2&gt;
  
  
  The point 🔗
&lt;/h2&gt;

&lt;p&gt;Is not to replace humans as the engineers but replace the grunt work. That said the pattern is implied by the use case. If I am building silly tools for myself, who cares what the code really looks like. If I am building functionality I have to rely on, I need to put considerably more agency in the matter. I will still offload the grunt work when possible but it still a practice. I would hope my carpenter would cut a few less corners on my cabinets than their own. It's not that we are lazy it's we exercise our agency in a way that is comfortable for us. What we build for others must be of the highest quality, what we build for ourselves needs to meet the need.&lt;/p&gt;

&lt;p&gt;I mean who knows what will happen and if greed will win and our work will be meat base robot pooper-scoopers. Until everyone figures it out get more work done and take a few more coffee breaks.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>architecture</category>
      <category>llm</category>
    </item>
    <item>
      <title>Rust Dancing ANSI Banana with Server-Sent Events</title>
      <dc:creator>Paul Scarrone</dc:creator>
      <pubDate>Sun, 01 Feb 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/paulscoder/rust-dancing-ansi-banana-with-server-sent-events-1nnd</link>
      <guid>https://dev.to/paulscoder/rust-dancing-ansi-banana-with-server-sent-events-1nnd</guid>
      <description>&lt;p&gt;&lt;strong&gt;Remember that dancing Ruby banana?&lt;/strong&gt; 🍌&lt;/p&gt;

&lt;p&gt;Well, I couldn't help myself. After building the &lt;a href="https://dev.to/i-made-a-thing/ruby-streaming-banana-dancer/"&gt;Ruby version with chunked transfer encoding&lt;/a&gt;, I started wondering: what if we explored the &lt;em&gt;other&lt;/em&gt; way to stream data to browsers and terminals? Enter the Rust implementation using Server-Sent Events.&lt;/p&gt;

&lt;p&gt;Yeah, I rewrote it in Rust. With SSE.&lt;/p&gt;

&lt;p&gt;So here's the thing: when you want to stream data from a server to clients, you've got options. My Ruby version uses chunked transfer encoding—basically HTTP/1.1's way of saying "I'm sending you data in pieces, and I'll tell you when each piece ends." But there's another player in town: Server-Sent Events (SSE), which is a proper protocol built on top of chunked encoding for one-way server-to-client streaming.&lt;/p&gt;

&lt;p&gt;Why both? Because understanding the difference matters when you're building real streaming applications. Plus, Rust's async ecosystem with Actix-Web makes SSE implementation surprisingly elegant.&lt;/p&gt;

&lt;p&gt;The best part? It works with both curl &lt;em&gt;and&lt;/em&gt; web browsers. Same endpoint, different experiences. Curl gets raw ANSI animations, browsers get properly formatted SSE streams. One server, two clients, zero compromise.&lt;/p&gt;

&lt;p&gt;Want to see how SSE differs from plain chunked encoding? Grab the code at &lt;a href="https://git.sr.ht/~ninjapanzer/sse-dancing-banana" rel="noopener noreferrer"&gt;sse-dancing-banana&lt;/a&gt; and follow along. Or if you just want to see a banana dance: &lt;code&gt;curl -N http://localhost:8080/live&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Bottom line: Sometimes the best way to learn a protocol is to make something completely silly with it. And what's sillier than making fruit dance in your terminal?&lt;/p&gt;




&lt;p&gt;Hope your terminal's ready for some Rust-powered dancing! 🍌🦀🎵&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdq389t143dyoqe6ykm3w.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdq389t143dyoqe6ykm3w.gif" alt="streaming-banana" width="600" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  DevLog 🔗
&lt;/h2&gt;

&lt;h3&gt;
  
  
  02 02 2026 🔗
&lt;/h3&gt;

&lt;h4&gt;
  
  
  SSE vs Chunked Encoding: What's the Difference? 🔗
&lt;/h4&gt;

&lt;p&gt;When I built the Ruby version, I used chunked transfer encoding directly. It's HTTP/1.1's mechanism for streaming—you send data in chunks, each prefixed with its size in hex, terminated by a zero-length chunk. Simple, direct, low-level.&lt;/p&gt;

&lt;p&gt;But SSE is different. It's a &lt;em&gt;protocol&lt;/em&gt; built on top of chunked encoding. Think of chunked encoding as the delivery truck, and SSE as the carefully labeled packages inside. SSE defines a specific text format for events:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data: &amp;lt;your content here&amp;gt;
data: &amp;lt;more content&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each event ends with a double newline. You can have multi-line data (prefix each line with &lt;code&gt;data:&lt;/code&gt;), event types, IDs for reconnection, even retry hints. It's structured, and browsers have native &lt;code&gt;EventSource&lt;/code&gt; API support.&lt;/p&gt;

&lt;p&gt;Here's how the Rust code handles both in the same endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async fn live(req: HttpRequest) -&amp;gt; impl Responder {
    let user_agent = req
        .headers()
        .get("User-Agent")
        .and_then(|h| h.to_str().ok())
        .unwrap_or("");

    let is_curl = user_agent.contains("curl");

    // ... speed parameter parsing ...

    let stream = stream::unfold(
        FrameStream { current: 0, interval, is_curl },
        move |mut state| async move {
            actix_web::rt::time::sleep(state.interval).await;
            if state.current &amp;gt;= FRAMES.len() {
                state.current = 0;
            }
            let frame = FRAMES[state.current];
            let data = state.format_frame_data(frame);
            state.current += 1;
            Some((
                Ok::&amp;lt;_, std::convert::Infallible&amp;gt;(web::Bytes::from(data)),
                state,
            ))
        },
    );

    HttpResponse::Ok()
        .content_type("text/event-stream")
        .streaming(stream)
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The magic happens in &lt;code&gt;format_frame_data&lt;/code&gt;. For curl, we send raw ANSI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn format_frame_data(&amp;amp;self, frame: &amp;amp;str) -&amp;gt; String {
    if self.is_curl {
        // Chunked encoding: just send the frame with ANSI clear codes
        format!("{}{}\n\n", ANSI_CLEAR, frame)
    } else {
        // SSE: format according to the SSE protocol
        let cleaned = self.strip_ansi(frame);
        let lines: Vec&amp;lt;&amp;amp;str&amp;gt; = cleaned.lines().collect();
        let sse_lines: Vec&amp;lt;String&amp;gt; = lines
            .iter()
            .map(|l| format!("data: {}", l))
            .collect();
        format!("{}\n\n", sse_lines.join("\n"))
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See the difference? For curl, we're just sending data. For browsers, we're wrapping each line in &lt;code&gt;data:&lt;/code&gt; prefixes and preserving the SSE format. The browser's &lt;code&gt;EventSource&lt;/code&gt; API automatically parses this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does this matter?&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Reconnection&lt;/strong&gt; : SSE includes automatic reconnection with &lt;code&gt;Last-Event-ID&lt;/code&gt;. Chunked encoding? You're on your own.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser Support&lt;/strong&gt; : &lt;code&gt;EventSource&lt;/code&gt; is built-in. Chunked encoding requires manual &lt;code&gt;fetch()&lt;/code&gt; streaming, which is newer and less supported.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event Types&lt;/strong&gt; : SSE lets you send different event types on the same stream. Chunked encoding is just bytes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplicity&lt;/strong&gt; : For server-to-client streaming, SSE handles the protocol. Chunked encoding is just the transport.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;When to use what?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Chunked Encoding&lt;/strong&gt; : When you need low-level control, binary data, or don't care about browser niceties. Think raw terminal streaming, like the Ruby version.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSE&lt;/strong&gt; : When you want browser compatibility, automatic reconnection, structured events, or you're building a real-time notification system.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this project, SSE won because I wanted both curl &lt;em&gt;and&lt;/em&gt; browser support without writing separate endpoints.&lt;/p&gt;

&lt;h3&gt;
  
  
  02 02 2026 1 🔗
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Rust's Async Streams: The Good Parts 🔗
&lt;/h4&gt;

&lt;p&gt;Coming from Ruby's Sinatra with its simple &lt;code&gt;stream&lt;/code&gt; block, I expected Rust to be painful. It wasn't.&lt;/p&gt;

&lt;p&gt;Actix-Web's streaming response is built on Rust's &lt;code&gt;Stream&lt;/code&gt; trait, which is like an async iterator. You create something that implements &lt;code&gt;Stream&lt;/code&gt;, and the framework handles the rest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct FrameStream {
    current: usize,
    interval: Duration,
    is_curl: bool,
}

impl Stream for FrameStream {
    type Item = Result&amp;lt;web::Bytes, std::convert::Infallible&amp;gt;;

    fn poll_next(mut self: Pin&amp;lt;&amp;amp;mut Self&amp;gt;, _cx: &amp;amp;mut Context&amp;lt;'_&amp;gt;)
        -&amp;gt; Poll&amp;lt;Option&amp;lt;Self::Item&amp;gt;&amp;gt;
    {
        if self.current &amp;gt;= FRAMES.len() {
            self.current = 0;
        }
        let frame = FRAMES[self.current];
        let data = self.format_frame_data(frame);
        self.current += 1;
        Poll::Ready(Some(Ok(web::Bytes::from(data))))
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But I took a shortcut. Instead of implementing &lt;code&gt;Stream&lt;/code&gt; manually, I used &lt;code&gt;stream::unfold&lt;/code&gt;, which is like &lt;code&gt;reduce&lt;/code&gt; but for streams:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let stream = stream::unfold(
    FrameStream { current: 0, interval, is_curl },
    move |mut state| async move {
        actix_web::rt::time::sleep(state.interval).await;
        // ... produce next item ...
        Some((Ok(web::Bytes::from(data)), state))
    },
);

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The state (&lt;code&gt;FrameStream&lt;/code&gt;) gets passed into the async block, which produces the next item and returns the updated state. Rinse, repeat, stream forever. It's elegant once you get past the types.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Rust Tax&lt;/strong&gt; : You pay upfront in type signatures (&lt;code&gt;Result&amp;lt;web::Bytes, std::convert::Infallible&amp;gt;&lt;/code&gt; for an infallible stream?), but you get safety and zero-cost abstractions. No runtime overhead for this streaming abstraction—it compiles down to a state machine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Ruby Comparison&lt;/strong&gt; : In Ruby's Sinatra, I did this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;stream(:keep_open) do |out|
  loop do
    out &amp;lt;&amp;lt; render_frame
    sleep 0.1
  end
end

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple, but you're managing the loop and sleep manually. Rust's &lt;code&gt;stream::unfold&lt;/code&gt; encodes that pattern into the type system. More verbose, but impossible to accidentally block the runtime or leak resources.&lt;/p&gt;

&lt;h3&gt;
  
  
  01 02 2026 🔗
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Compile-Time Frame Embedding 🔗
&lt;/h4&gt;

&lt;p&gt;One detail I'm proud of: the frames are embedded at compile time using &lt;code&gt;include_str!&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const FRAMES: [&amp;amp;str; 8] = [
    include_str!("../../assets/frames/frame0.txt"),
    include_str!("../../assets/frames/frame1.txt"),
    include_str!("../../assets/frames/frame2.txt"),
    include_str!("../../assets/frames/frame3.txt"),
    include_str!("../../assets/frames/frame4.txt"),
    include_str!("../../assets/frames/frame5.txt"),
    include_str!("../../assets/frames/frame6.txt"),
    include_str!("../../assets/frames/frame7.txt"),
];

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No runtime file I/O. No error handling for missing files in production. The frames are literally part of the compiled binary, stored in the &lt;code&gt;.rodata&lt;/code&gt; section. If the files don't exist at compile time, the build fails. Hard fail at compile time beats mysterious runtime errors.&lt;/p&gt;

&lt;p&gt;In Ruby, I loaded frames at runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;frames = Dir.glob("ascii_frames/*.txt").sort.map { |f| File.read(f) }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works, but it's runtime overhead, potential I/O errors, and requires the filesystem to be available. For a simple animation, compile-time embedding is perfect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trade-off&lt;/strong&gt; : Binary size increases by ~8 text files. For a banana animation, I'll take it.&lt;/p&gt;

&lt;h3&gt;
  
  
  01 02 2026 1 🔗
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Nix for Rust: Less Painful Than Ruby 🔗
&lt;/h4&gt;

&lt;p&gt;After fighting Nix for the Ruby version's gem dependencies, Rust was refreshing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;outputs = { self, nixpkgs, ... }:
  let
    system = "x86_64-linux";
    pkgs = import nixpkgs { inherit system; };
  in {
    devShells.${system}.default = pkgs.mkShell {
      buildInputs = with pkgs; [
        rustc
        cargo
        rust-analyzer
      ];
    };
  };

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Cargo handles dependencies via &lt;code&gt;Cargo.lock&lt;/code&gt;, which Nix respects. No gemset.nix translation layer, no bundlerEnv complexity. Rust's deterministic builds align perfectly with Nix's philosophy.&lt;/p&gt;

&lt;p&gt;For production, I'd add &lt;code&gt;pkgs.buildRustPackage&lt;/code&gt;, but for local dev? This simple shell is all you need.&lt;/p&gt;

&lt;p&gt;The Rust ecosystem's commitment to reproducible builds (via Cargo.lock) makes Nix integration almost trivial. Ruby's dynamic nature fights Nix at every turn. This is one of those moments where Rust's compile-time philosophy pays dividends.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>rust</category>
      <category>showdev</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Lets get AI to finish its work</title>
      <dc:creator>Paul Scarrone</dc:creator>
      <pubDate>Tue, 27 Jan 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/paulscoder/a-deterministic-box-for-non-deterministic-engines-55f5</link>
      <guid>https://dev.to/paulscoder/a-deterministic-box-for-non-deterministic-engines-55f5</guid>
      <description>&lt;h2&gt;
  
  
  The Nature of Non-Determinism with LLMs 🔗
&lt;/h2&gt;

&lt;p&gt;So you may have heard of weights, biases, and temperature when LLMs are described. For the uninitiated: weights and biases are the core parameters learned during training that encode the model's knowledge, while temperature is an inference-time parameter that controls how much variance appears in the model's outputs. Higher temperature means more randomness in token selection; lower temperature means more deterministic responses. It's exactly this temperature parameter that ensures the model will respond with some variance for the same input. So that's clearly this non-determinism which flies in the face of the normal expectation of computers, but it's this that also provides some of the nuance in token prediction that makes the LLM work so it's easy to identify this as an &lt;strong&gt;Architectural Trade-Off&lt;/strong&gt; and not necessarily a &lt;strong&gt;Detractor&lt;/strong&gt;. So hoping that provides some grounding let's talk about how to make good use of this engine of... making shit up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making Shit Up 🔗
&lt;/h3&gt;

&lt;p&gt;Yep, so that's not a tradeoff, it's a flaw, one we haven't solved yet. When the context is ambiguous the model chooses to do one of two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Just pretend it didn't hear what it was asked to do&lt;/li&gt;
&lt;li&gt;Make shit up, hallucinations&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Of course I think the former is not talked about as much as the hallucinations. Not to mention that the hallucinations are harder to detect and protect. Note that hallucinations are actually a separate problem from non-determinism - they're about confidence miscalibration and training data limitations, not temperature variance. Hallucinations can occur even with low temperature settings. But we can take a stab at it with some extra prompting and extra runtimes at the cost of tokens. Don't get too upset this is just the normals of computers, we make a simple thing and it has sharp edges, so we make more things that consume some extra energy to constrain the first.&lt;/p&gt;

&lt;p&gt;Usually, these are to solve for the inefficiency of the human communication, but sometimes it's just cause people wanna abuse it. I like to think of Auth as a regular pain point we don't really need but have to have because trust is a hard problem. Most of whats on the web doesn't need centralized authentication but GPG has always been too hard so we made something easier to understand.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do? 🔗
&lt;/h2&gt;

&lt;p&gt;Ok, back to the question, well I call it micromanagement but that kind of implies that the model and its agents have some kind of human agency, which they don't. Although some of their processes are directly modeled after humans so we can loosely apply some techniques to rein them in.&lt;/p&gt;

&lt;p&gt;First, let's talk about context and ambiguity. If you haven't figured this out yet the longer the context the more the model's attention distributes across tokens, reducing precision on individual details - a "lost-in-the-middle" effect where information gets deprioritized. Most of this is your fault because even with your best effort you introduce inconsistencies and other inaccuracies into the conversation. The lesson, clear your context often and especially between phases of your work, aka, planning, building, and verifying. I like to consider this an analogy to writing and editing. Have someone else edit your work or write it and review it a week later to improve objectivity. Thankfully with LLMs their memory is as ephemeral as you like.&lt;/p&gt;

&lt;p&gt;So we need a way to turn a goal into a workstream that allows us to actually look away from the model's stream. Some might call this an agentic orchestration but I feel these often sprint from meaningful to overly complicated in a matter of weeks. Especially if you use something like Claude-Code, Codex, or OpenCode all the building tools are there already. So starting from something like Claude-Code we need to teach our main agent interface to better follow some process when working.&lt;/p&gt;

&lt;p&gt;Here is an example:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CLAUDE.MD&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
## Working Style

When collaborating on this project:
- Check existing files first before suggesting changes
- Ask questions one at a time to refine ideas
- Prefer multiple choice questions when possible
- Focus on understanding: purpose, constraints, success criteria
- Apply YAGNI ruthlessly - remove unnecessary features from all designs
- Present designs in sections and validate each incrementally
- Go back and clarify when something doesn't make sense

## Deliverables

- Break down the decisions from collaboration into tasks
- You must use any defined task tracking tools outlined in the Task Tracking section to create tasks falling back to markdown files if nothing is defined
- Create a report for the executiong plan with dependencies mapped

## Workflow Guidelines

- Create an epic for each high-level objective
- Create subtasks as a todo chain under the epic
- Write titles as the task to be performed (imperative form)
- Add detailed descriptions with examples of work to be done
- Verify each task before closing
- Log details about failures and retries in ticket descriptions for historical tracking
- When an epic is completed, write a report of the task graph and verify all items were performed

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Controlling Core Memories 🔗
&lt;/h3&gt;

&lt;p&gt;As I included above &lt;em&gt;Deliverables&lt;/em&gt; and &lt;em&gt;Workflow Guidelines&lt;/em&gt; we initially want our first pass to be on work breakdown and dependency. This will provide some added benefits the way we will track that work progress though. Often the agent writing code falls victim to the two points above with a couple of variations. Hallucinations in this case are items that just don't work and the remainder is missed features. That's good though because we can track and essentially later interrogate success and failure of the model's execution. Better yet we can finally realize the age old dream that we can repeat a variation of a task in the future more accurately because each replanning is less ambiguous. Good luck doing this with people but with LLMs it's all data.&lt;/p&gt;

&lt;p&gt;So memory management moves into tasks, which can be in markdown, Jira via MCP (Model Context Protocol - a standard for connecting AI agents to external tools), or my preference, &lt;a href="https://github.com/steveyegge/beads" rel="noopener noreferrer"&gt;Beads&lt;/a&gt; I don't think there is a lot of big effective differences for me except when we come back to the nature of context size complication introducing confusion.&lt;/p&gt;

&lt;p&gt;So beads does for AI what Jira does for humans and yet even as a human I would rather use Beads than Jira. Arguably, the difference is that tools like Beads focus on de-complicating the organization of work, its there for the worker's benefit. Jira on the other hand only benefits the bean counters and the workers just have to suffer so that a very few can complain that the reports it produces are useless.&lt;/p&gt;

&lt;p&gt;Sorry, my Jira PTSD is showing... Beads, right Beads lets the coding agent take its task breakdown and put it into a graph with dependencies and epics, these feel meaningless to the agent but it's more about what we get to do with it later. It's easier for me to say to a fresh context, review the epic X and verify its functionality. You'll notice that when it finds something is a failure it usually just tries to fix it but it's also going to record a stream of attempts and what was the final resolution. Resulting in a history of the model's confusion introduced from me or the plan, but when I wanna do something similar I can use the JSONL (JSON Lines format - one JSON object per line) from the beads sync operation to prompt a variation of the task and create a new task breakdown.&lt;/p&gt;

&lt;p&gt;Here is a claude partial to explain beads&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;### Task tracking

Use 'bd' (beads) for task tracking. Run `bd onboard` to get started.

#### bd Quick Reference

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
bash&lt;/p&gt;
&lt;h1&gt;
  
  
  Discovery &amp;amp; Navigation
&lt;/h1&gt;

&lt;p&gt;bd ready # Find available work&lt;br&gt;
bd show  # View issue details&lt;br&gt;
bd show  --children # Show issue with subtasks&lt;/p&gt;
&lt;h1&gt;
  
  
  Task Management
&lt;/h1&gt;

&lt;p&gt;bd create "&lt;/p&gt;" --type epic # Create an epic&lt;br&gt;
bd create "" --parent  # Create subtask under parent&lt;br&gt;
bd update  --description "..." # Update description&lt;br&gt;
bd update  --status in_progress # Claim work&lt;br&gt;
bd close  # Complete work
&lt;h1&gt;
  
  
  Sync &amp;amp; Persistence
&lt;/h1&gt;

&lt;p&gt;bd sync # Sync with git (exports to JSONL)&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
#### Workflow Guidelines

- Create an epic for each high-level objective
- Create subtasks as a todo chain under the epic
- Write titles as the task to be performed (imperative form)
- Add detailed descriptions with examples of work to be done
- Verify each task before closing
- Log details about failures and retries in ticket descriptions for historical tracking
- When an epic is completed, write a report of the task graph and verify all items were performed

#### Displaying Task Graphs

Use `bd show &amp;lt;epic-id&amp;gt; --children` to display the task hierarchy. For visual reports, create ASCII diagrams showing task dependencies and completion status.

&lt;/code&gt;&lt;/pre&gt;



&lt;h2&gt;
  
  
  Uniqueness vs Repeatability 🔗
&lt;/h2&gt;

&lt;p&gt;This is kind of the funny part of this whole process, the LLM can help with a bespoke task but it doesn't generally improve performance because the context size tends to bias towards failures and you end up having to check its outputs and re-validate anything ambiguous. You may say that you don't need to, but just look at the news, it's the failure mode the AI tools get lambasted on. Of course being an engineer we know that everything is essentially wrong and we are balancing the acceptable amount of wrong we can accept at any given moment.&lt;/p&gt;

&lt;p&gt;This of course means that when we can find a process that is refinable to a predictable set of tasks we will end up trying to build some complicated brittle script that can automate the process and here is why building things with computers can be kinda dry. We should let the models handle the fixed set of tasks that need a little flexibility but doesn't offer too much range of opportunities for errors.&lt;/p&gt;

&lt;p&gt;Refinement of process from memory is just a strategy but it's one that works quite well since the next agent can read the actions of its predecessor, you can bias it to take the success path and start ignoring it, which is the dream. For years I have been using LLMs and finding myself trapped staring at the console because it prompts me for feedback every couple of seconds building context or I have to endlessly remind it to complete the tasks. Both of these conditions are mostly eliminated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploying to K8s 🔗
&lt;/h3&gt;

&lt;p&gt;A concrete example of this is to deploy an application to kubernetes. This is super well documented and there is a ton of good tooling but it's also a highly configurable system. Each enterprise makes its own rules and policies around how containers are described. It can be very tiresome reading charts and chart documentation while bashing your head against a statement like &lt;em&gt;CrashBackoffLoop&lt;/em&gt;. It's not like there isn't a way to learn about what's happening but it's a lot of command orchestration, the LLM can collect context of the failure much better since it can read multiple streams concurrently. So I recently deployed &lt;a href="https://flagd.dev" rel="noopener noreferrer"&gt;flagd&lt;/a&gt; (an open-source feature flag daemon) to our cluster and let Claude handle the building of charts. To keep things simple I let it just write the deployment and service artifacts itself. It failed a bunch but eventually determined from feedback of the k8s MCP that we were missing a config file. It mounted a volume and created a config map, 20 minutes later I have a stable instance running. I will need to do some work to make this fit into organizational norms but I am also ready for my PoC at the same time. I also have a log of what worked and what didn't work so when it comes time to deploy something else to our cluster I have a baseline of tasks which will reduce the planning time and token volume.&lt;/p&gt;

&lt;p&gt;An example of this completed task output:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Beads Task Report - January 26, 2026

Epic: Deploy flagd to namespace

ID: feature-flag-prd-q6w | Status: ✅ CLOSED

---
Task Graph

feature-flag-prd-q6w (EPIC) ✅ CLOSED
├── .1 Create flagd Deployment manifest ✅ CLOSED
├── .2 Create flagd Service manifest ✅ CLOSED
├── .3 Deploy flagd Deployment to cluster ✅ CLOSED
├── .4 Deploy flagd Service to cluster ✅ CLOSED
└── .5 Verify flagd deployment is running ✅ CLOSED

---
Task Completion Summary
┌─────┬────────────────────────────┬────────┬───────────────────────────────────────────┐
│ ID │ Task │ Status │ Close Reason │
├─────┼────────────────────────────┼────────┼───────────────────────────────────────────┤
│ .1 │ Create Deployment manifest │ ✅ │ flagd_deploy/k8s/deployment.yaml created │
├─────┼────────────────────────────┼────────┼───────────────────────────────────────────┤
│ .2 │ Create Service manifest │ ✅ │ flagd_deploy/k8s/service.yaml created │
├─────┼────────────────────────────┼────────┼───────────────────────────────────────────┤
│ .3 │ Deploy Deployment │ ✅ │ Deployment exists in cluster │
├─────┼────────────────────────────┼────────┼───────────────────────────────────────────┤
│ .4 │ Deploy Service │ ✅ │ Service exists with ClusterIP 127.0.0.1 │
├─────┼────────────────────────────┼────────┼───────────────────────────────────────────┤
│ .5 │ Verify deployment running │ ✅ │ Pod running after troubleshooting │
└─────┴────────────────────────────┴────────┴───────────────────────────────────────────┘
---
Troubleshooting Log (Task .5)

The verification task encountered issues that required 3 attempts:

Attempt 1 - CrashLoopBackOff
- Container exited with code 0 after printing help
- Fix: Added args: [start] to deployment

Attempt 2 - Fatal: no sync implementation set
- flagd requires a flag source (file, gRPC, or HTTP sync)
- Fix: Created ConfigMap with example flags, added volume mount

Attempt 3 - SUCCESS ✓
- Created ConfigMap: accounts-utility-flagd-service-dev-flags
- Updated deployment with volume mount to /etc/flagd
- Added args: --uri file:/etc/flagd/flags.json

---
Final Deployment State
┌───────────────┬────────────────────────────────────────────────────┐
│ Component │ Value │
├───────────────┼────────────────────────────────────────────────────┤
│ Pod │ flagd-service-dev-87fb5946f-vft4t │
├───────────────┼────────────────────────────────────────────────────┤
│ Status │ 1/1 Running │
├───────────────┼────────────────────────────────────────────────────┤
│ Pod IP │ 10.10.10.102 │
├───────────────┼────────────────────────────────────────────────────┤
│ Service IP │ 172.10.10.10 │
├───────────────┼────────────────────────────────────────────────────┤
│ Node │ node │
├───────────────┼────────────────────────────────────────────────────┤
│ flagd Version │ v0.13.2 │
└───────────────┴────────────────────────────────────────────────────┘
Exposed Ports:
- gRPC: 8013
- HTTP: 8014
- FlagSync: 8015
- OFREP: 8016

---
Result: 5/5 tasks completed - Epic successfully closed

&lt;/code&gt;&lt;/pre&gt;



&lt;h2&gt;
  
  
  What next 🔗
&lt;/h2&gt;

&lt;p&gt;Here is how I would go about things, start by recording your plans. Maybe take one of my examples and refine it for you and check your experiences. Then install Beads and just manually create tasks and see how the agent interacts. Then go ahead an automate the whole thing but maybe this time we can avoid &lt;a href="https://xkcd.com/1319/" rel="noopener noreferrer"&gt;xkcd:1319&lt;/a&gt; but probably not :)&lt;/p&gt;

</description>
      <category>ai</category>
      <category>computerscience</category>
      <category>llm</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>The Magic of Stubbing sh</title>
      <dc:creator>Paul Scarrone</dc:creator>
      <pubDate>Fri, 10 Oct 2025 01:34:25 +0000</pubDate>
      <link>https://dev.to/paulscoder/the-magic-of-stubbing-sh-567m</link>
      <guid>https://dev.to/paulscoder/the-magic-of-stubbing-sh-567m</guid>
      <description>&lt;p&gt;I really love sh and bash but I often feel alone and I get some regular negativity when I solve a problem with it. I know why too, shell scripts can have a broad level of complexity that has other languages embedded into it. But its not as esoteric as you might think, more another domain we should be comfortable with. One of the ways I learned to deal with unknown domains was to read the tests. Because tests tend to use some common language they are often more literate. Here's the thing, I keep getting people tell me that shell scripts don't have tests, and they are wrong. See I have this trick, its called BATS and I talked about it over here &lt;a href="https://developmeh.com/tech-dives/test-anything-means-testing-bash" rel="noopener noreferrer"&gt;Test Anything Protocol&lt;/a&gt; where I showed an example of stubbing &lt;code&gt;helm&lt;/code&gt; but that example was not the whole story. Since the BATS framework is itself bash we have all those nasty tools at our disposal to manipulate our subject under test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Subject Under Test
&lt;/h2&gt;

&lt;p&gt;Boring as it may be the purpose here is to observe and verify the output and side-effects of commands run by the shell. We need to respect this boundary between our scripts and the tests for those scripts. One of the challenges to this is how commands avoid observation like &lt;code&gt;rm&lt;/code&gt; &lt;code&gt;mktemp&lt;/code&gt;, if my script creates a tempfile and then removes it it’s hard to verify if that step occurred without modifying the subject. Of course we can write traces to &lt;code&gt;&amp;amp;&amp;gt;2&lt;/code&gt; using echo but that proves nothing more than the presence of the echo statement. I need to verify the validity of these intermediate steps. In traditional programming languages we have mocks and spies which capture the fundamental flow of the code by interfering with the call sites and through reflection. We can do something similar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mocking or Stubbing... Whatever
&lt;/h2&gt;

&lt;p&gt;Now there are BATS mocking libraries and they are a wondrous cornucopia of features but in my experience they don't expose much more than a new way of describing, a DSL, how to intercept and modify interactions. So go learn and use those, but for many normal use cases I wanna show you how to do this by hand and use the existing shell language you already know. In the following example we are going to observe tempfiles so we can keep track of an intermediate state, while exposing debugging information when doing TDD, more on that down the line though.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;temp.sh&lt;/strong&gt; Subject Under Test&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash -e&lt;/span&gt;

&lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;workspace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;mktemp&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;touch&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$workspace&lt;/span&gt;&lt;span class="s2"&gt;/not_temp.sh"&lt;/span&gt;

&lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;first&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;mktemp&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;second&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;mktemp&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"WOW"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$second&lt;/span&gt;

&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nv"&gt;$first&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nv"&gt;$second&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;temp.sh.bats&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bats&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; +x

bats_require_minimum_version 1.5.0

&lt;span class="c"&gt;# Load Bats libraries&lt;/span&gt;
load ../../.test/bats/bats-support/load
load ../../.test/bats/bats-assert/load

&lt;span class="c"&gt;# Stub rm to capture files deleted&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;arg &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$arg&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; -&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
      &lt;/span&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$arg&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_DIRECTORY_RUNNING&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/tmp/&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$arg&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;.captured"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;return &lt;/span&gt;0
    &lt;span class="k"&gt;fi
  done
  &lt;/span&gt;&lt;span class="nb"&gt;command rm&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Stub mktemp to track temp files for cleanup&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;&lt;span class="nb"&gt;mktemp&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;tmp
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"-d"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_DIRECTORY_RUNNING&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; counter &amp;lt; &lt;span class="nv"&gt;$TEMPS_COUNTER&lt;/span&gt;
    &lt;span class="o"&gt;((&lt;/span&gt;counter++&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="k"&gt;$((&lt;/span&gt;counter&lt;span class="k"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$TEMPS_COUNTER&lt;/span&gt;
    &lt;span class="nv"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_DIRECTORY_RUNNING&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/tmp/bats.&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;counter&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$tmp&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$TEMPS&lt;/span&gt;
  &lt;span class="k"&gt;fi
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$tmp&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

setup&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TEST_DIRECTORY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"./.tests/res"&lt;/span&gt;
  &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TEST_DIRECTORY_RUNNING&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"./.tests/res_tmp"&lt;/span&gt;
  &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TEMPS_COUNTER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_DIRECTORY_RUNNING&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/tmp/.counter
  &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TEMPS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_DIRECTORY_RUNNING&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/tmp/.temps
  &lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_DIRECTORY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/."&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_DIRECTORY_RUNNING&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/"&lt;/span&gt;
  &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_DIRECTORY_RUNNING&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/tmp"&lt;/span&gt;
  &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nb"&gt;mktemp
  export&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nb"&gt;rm

  touch&lt;/span&gt; &lt;span class="nv"&gt;$TEMPS_COUNTER&lt;/span&gt;
  &lt;span class="nb"&gt;touch&lt;/span&gt; &lt;span class="nv"&gt;$TEMPS&lt;/span&gt;
  &lt;span class="nb"&gt;echo &lt;/span&gt;0 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$TEMPS_COUNTER&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

teardown&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;tmp &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;temps&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nb"&gt;command rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$tmp&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;done

  &lt;/span&gt;&lt;span class="nb"&gt;unset&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nb"&gt;mktemp
  unset&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nb"&gt;rm

  command rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TEMPS_COUNTER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;command rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TEMPS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="nb"&gt;unset &lt;/span&gt;TEST_DIRECTORY
  &lt;span class="nb"&gt;unset &lt;/span&gt;TEST_DIRECTORY_RUNNING
  &lt;span class="nb"&gt;unset &lt;/span&gt;TEMPS_COUNTER
  &lt;span class="nb"&gt;unset &lt;/span&gt;TEMPS
&lt;span class="o"&gt;}&lt;/span&gt;

@test &lt;span class="s1"&gt;'test intermediate files'&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;second_tempfile_expected&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"WOW"&lt;/span&gt;
  run bash ./.tests/temp.sh

  &lt;span class="c"&gt;# note the captured&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;second_tempfile_actual&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_DIRECTORY_RUNNING&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/tmp/bats.2.captured&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  assert_success

  assert_equal &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TEMPS_COUNTER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; 2
  assert_equal &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;$TEST_DIRECTORY_RUNNING&lt;/span&gt;/not_temp.sh &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;0 &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;1&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 0
  assert_equal &lt;span class="nv"&gt;$second_tempfile_actual&lt;/span&gt; &lt;span class="c"&gt;#second_tempfile_expected&lt;/span&gt;
  assert_output &lt;span class="nt"&gt;--regexp&lt;/span&gt; &lt;span class="s1"&gt;'Done'&lt;/span&gt;

  &lt;span class="c"&gt;# _Note_ The use of `command` which bypasses our function export of `rm` introduced by `export -f rm` this makes sure we use the original command and not our mock.&lt;/span&gt;
    &lt;span class="nb"&gt;command rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_DIRECTORY_RUNNING&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lets explore the mocking... ignoring the directory paths we intercept calls to mktemp and if the commands first argument is &lt;code&gt;-d&lt;/code&gt; for directory we inject a static location we control. Otherwise we create a unique file in that directory. When we do this we capture the temp file and the number created so far so we can verify the interfaction later. Both these files can be observed during execution.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Stub mktemp to track temp files for cleanup&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;&lt;span class="nb"&gt;mktemp&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;tmp
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"-d"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_DIRECTORY_RUNNING&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; counter &amp;lt; &lt;span class="nv"&gt;$TEMPS_COUNTER&lt;/span&gt;
    &lt;span class="o"&gt;((&lt;/span&gt;counter++&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="k"&gt;$((&lt;/span&gt;counter&lt;span class="k"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$TEMPS_COUNTER&lt;/span&gt;
    &lt;span class="nv"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_DIRECTORY_RUNNING&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/tmp/bats.&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;counter&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$tmp&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$TEMPS&lt;/span&gt;
  &lt;span class="k"&gt;fi
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$tmp&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we write clean scripts we also clean up after ourselves this good behavior provides a challenge to checking the contents of these intermediate files. Because shell scripts are file system based the most common way for data to make its way between processes is to write and read from the filesystem. But if we are tracing a bug in our code we have to regularly interfere with out subject under test to observe its intermediate steps. But if we capture the &lt;code&gt;rm&lt;/code&gt; command we can conditionally retain some of the progress. In this example we capture all the args and if one includes a path we extract the filename, append &lt;code&gt;.captured&lt;/code&gt; and copy it to our running directory. Ultimately, even if we don't stub mktemp we can still capture deleted tempfiles this way.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note&lt;/em&gt; The use of &lt;code&gt;command&lt;/code&gt; which bypasses our function export of &lt;code&gt;rm&lt;/code&gt; introduced by &lt;code&gt;export -f rm&lt;/code&gt; makes sure we use the original command and not our mock.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Stub rm to capture files deleted&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;arg &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$arg&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; -&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
      &lt;/span&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$arg&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_DIRECTORY_RUNNING&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/tmp/&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$arg&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;.captured"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;return &lt;/span&gt;0
    &lt;span class="k"&gt;fi
  done
  &lt;/span&gt;&lt;span class="nb"&gt;command rm&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now lets review the test, first we can do traditional expectation with the assert module following the standard, Given, When, Then structure we love. Let's look at how the When is structured too, because this is bash whichever assertion fails the program will exit there. So note the last line where we clean up the temp directory for the test. By leaving this as the last statement we keep the test artifacts if the test fails. Which enables better TDD, where we write a test that fails and continue to iterate until that test passes, meanwhile the test is also producing trace and debugging information about our work. We can do this with any command though, say we call &lt;code&gt;git diff&lt;/code&gt; and we want to verify what we produced. We can intercept any command and have it write a file to our test workspace. Importantly, while not changing the subject under test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;@test &lt;span class="s1"&gt;'test intermediate files'&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;# Given&lt;/span&gt;
    &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;second_tempfile_expected&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"WOW"&lt;/span&gt;

  &lt;span class="c"&gt;# When&lt;/span&gt;
  run bash ./.tests/temp.sh

  &lt;span class="c"&gt;# Then&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;second_tempfile_actual&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_DIRECTORY_RUNNING&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/tmp/bats.2.captured&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  assert_success

  assert_equal &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TEMPS_COUNTER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; 2
  assert_equal &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;$TEST_DIRECTORY_RUNNING&lt;/span&gt;/not_temp.sh &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;0 &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;1&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 0
  assert_equal &lt;span class="nv"&gt;$second_tempfile_actual&lt;/span&gt; &lt;span class="c"&gt;#second_tempfile_expected&lt;/span&gt;
  assert_output &lt;span class="nt"&gt;--regexp&lt;/span&gt; &lt;span class="s1"&gt;'Done'&lt;/span&gt;

  &lt;span class="c"&gt;# _Note_ The use of `command` which bypasses our function export of `rm` introduced by `export -f rm` this makes sure we use the original command and not our mock.&lt;/span&gt;
    &lt;span class="nb"&gt;command rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEST_DIRECTORY_RUNNING&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Just Test Things and Be Happy
&lt;/h2&gt;

&lt;p&gt;This is just one dumb example of how to think about your testing and how to build up useful tooling that caters to your work. Now go write some bash and make sure you test it, trust me orchestrating a call to &lt;code&gt;git&lt;/code&gt; is 10 times easier than screwing around with some git integration for your language of choice. These tools were meant to work together in the shell and you will be happier just getting things done. Double happy when you can prove it works with a test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Errata
&lt;/h2&gt;

&lt;h3&gt;
  
  
  sh is not bash and vice versa
&lt;/h3&gt;

&lt;p&gt;While not functionally errors, the title of this work should be focused on bash. Since a lot of the sample code are bash-isms especially &lt;em&gt;exported functions&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  the sh alias and CI
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;run sh ./.tests/temp.sh&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;sh&lt;/code&gt; is often an alias on modern systems and this can have a huge impact when you scripts run in CI or more namely a non-interactive or non-login session. Where you CI might offer an Ubuntu or Alpine Linux image that provides &lt;code&gt;bash&lt;/code&gt; as an alias for &lt;code&gt;sh&lt;/code&gt; it may use a lighter weight implementation like &lt;code&gt;dash&lt;/code&gt; when running your tests. Because we are using features that are explicitly bash we should have our test suite &lt;code&gt;run bash ./.tests/temp.sh&lt;/code&gt; as such I have altered the above example accordingly.&lt;/p&gt;

</description>
      <category>bash</category>
      <category>testing</category>
      <category>shell</category>
      <category>tdd</category>
    </item>
    <item>
      <title>Is Pragmatism a Dogma in Software?</title>
      <dc:creator>Paul Scarrone</dc:creator>
      <pubDate>Sat, 08 Jan 2022 18:14:46 +0000</pubDate>
      <link>https://dev.to/paulscoder/is-pragmatism-a-dogma-in-software-d1c</link>
      <guid>https://dev.to/paulscoder/is-pragmatism-a-dogma-in-software-d1c</guid>
      <description>&lt;p&gt;Considering the influence of the seminal text, The Pragmatic Programmer, I often consider the intention of the lessons within and the culture, dare I say dogma of pragmatism in the software scene.&lt;/p&gt;

&lt;p&gt;If we consider that Pragmatism, loosely defined, is an assertion that objective reality is best viewed in terms of their practical uses and successes(&lt;a href="https://en.wikipedia.org/wik/Pragmatism" rel="noopener noreferrer"&gt;Wikipedia&lt;/a&gt;). Specifically, in relation to software development, we as developers are responsible for making features “work” in a pluralistic, problems-oriented environment. Let's explore the complications of this implied pluralism.&lt;/p&gt;

&lt;p&gt;It might be easy to only view the natures of pragmatism in software as competitive, but development is a considerably flexible act where conflicts can be moderated by perspective change. Nonetheless, here is a non-exhaustive list for consideration.&lt;/p&gt;

&lt;p&gt;Quality, Cost, Time Model&lt;br&gt;
Product Accuracy / Solution Completeness&lt;br&gt;
Risk&lt;br&gt;
Entropy&lt;br&gt;
Communication&lt;br&gt;
Prototyping&lt;br&gt;
Design Orthogonality&lt;br&gt;
You might be catching on to the fundamental complexity that an individual IC(Individual Contributor) might encounter when walking the path of pragmatism can be biased. Regardless if this is due to opinion, wisdom, knowledge or directed by leadership and culture, pragmatism is too broad to observe.&lt;/p&gt;

&lt;p&gt;So, here is where we encroach on dogma, and its toxic impact on software development. Pragmatism is a complex topic that is often “simply” explained. Like any sufficiently complex concept, practice and study, is our best defense against erroneous opinions. The risk we run is not having a holistic approach to identifying and repeating pragmatism.&lt;/p&gt;

&lt;p&gt;So, I figure this was a good opportunity to share something I found while researching a larger body of work, &lt;a href="https://github.com/HugoMatilla/The-Pragmatic-Programmer" rel="noopener noreferrer"&gt;https://github.com/HugoMatilla/The-Pragmatic-Programmer&lt;/a&gt;. Within is a lovely abridgment of the lessons from the book. Now let's compare and contrast the philosophy and the reality of pragmatism.&lt;/p&gt;

&lt;p&gt;I think we often approach solving conflicts like the fundamental application of pragmatism by exploring case studies, but since ideologically it's a philosophy, we should try our hands at building a tool set to better understand how we consume it. So instead, we can try to better identify if we are following the practice or the ideal.&lt;/p&gt;

&lt;p&gt;Some questions I ask myself:&lt;/p&gt;

&lt;p&gt;Am I sufficiently confident that the problem is well-defined?&lt;br&gt;
Am I chasing the problem as opposed to fixing it?&lt;br&gt;
Do I need to take into account violations of the “Law of Demeter”(&lt;a href="https://github.com/HugoMatilla/The-Pragmatic-Programmer#the-law-of-demeter-for-functions" rel="noopener noreferrer"&gt;Prag&lt;/a&gt;)?&lt;br&gt;
Have I reviewed the current solution before I started (Maintenance/Enhancement)?&lt;br&gt;
Do I understand the contracts in play(&lt;a href="https://github.com/HugoMatilla/The-Pragmatic-Programmer#26-decoupling-and-the-law-of-demeter" rel="noopener noreferrer"&gt;Prag&lt;/a&gt;)?&lt;br&gt;
Can I produce assertions for the change to be produced?&lt;br&gt;
Does this make me want to introduce a refactor pass?&lt;br&gt;
Now let's dig in a little further and try to extrapolate the pragmatic outcomes from these questions.&lt;/p&gt;

&lt;p&gt;The first two address reactivity and competency to start working. It's a measure twice and write once mentality. Pragmatism as a dogma of small and simple often pushes for action as opposed to development of intention and a plan. Problems, frequently, are not isolated, they are part of a system that takes care to understand. Some context clues here, frequency of code change in the target domain and perceived complexity of the code.&lt;/p&gt;

&lt;p&gt;If the code seems to have a lot of collaborators to complete single actions, alarms should be raised that you need to be prepared for more effort. Some context clues are; the combination of delegation, singleton, and inheritance within the same module. Numerous function which have the same or similar name with variable arity.&lt;/p&gt;

&lt;p&gt;The rest are about training myself not to provide a commitment to delivery until I have reviewed the existing solution. Preferably I would like to approach problems using TDD (Test Driven Development) but let's be honest we are going to be lucky if we know enough about the solution to write blind tests for a sufficiently complex system. This is approaching a completely different topic but, tests contain assertions and if we use the provided text for guidance here, assertions are the secret sauce. The tricky part is that assertions in practice produce an exceptional state when violated. If we choose to handle these states or use guard patterns, we must assure that they are not introducing or hiding side effects.&lt;/p&gt;

&lt;p&gt;Software developers have many roles, but the primary one will always be Change Management, and here is where I come to my last observation / concept, refactoring. Like all things “agile”, refactoring should be progressive changes to reduce risk or omitted entirely. The choice to refactor and when is the most powerful inherent benefit of pragmatic decision-making. All refactors should be considered non-trivial actions unless a suitably concrete case can be produced that would produce a positive constant multiplier of value. It's best to document ugly code, learn from its failures and leave it be, then seek the idealistic justice of a refactor. The simple litmus test is if you don't feel you could go to your manager and propose the refactors benefit to the business, you probably don't need it.&lt;/p&gt;

&lt;p&gt;If there is something here you disagree with, then that's probably a good thing. Our source material, first published in 1999, like any influential text, has to be constantly measured for validity against our current lives and careers. If we do not, we risk allowing the ideal of pragmatism evolve into a dogma without the value of its fundamental meaning.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>pragmatism</category>
      <category>craft</category>
      <category>books</category>
    </item>
  </channel>
</rss>
