<?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: Ian Johnson</title>
    <description>The latest articles on DEV Community by Ian Johnson (@tacoda).</description>
    <link>https://dev.to/tacoda</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F171498%2Fb1207a6e-f740-43c4-bb64-c675e3b3ce1d.jpeg</url>
      <title>DEV Community: Ian Johnson</title>
      <link>https://dev.to/tacoda</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tacoda"/>
    <language>en</language>
    <item>
      <title>The Governance Gap Is a Boundary Problem</title>
      <dc:creator>Ian Johnson</dc:creator>
      <pubDate>Wed, 08 Jul 2026 15:24:45 +0000</pubDate>
      <link>https://dev.to/tacoda/the-governance-gap-is-a-boundary-problem-gb</link>
      <guid>https://dev.to/tacoda/the-governance-gap-is-a-boundary-problem-gb</guid>
      <description>&lt;p&gt;One of my early factory prototypes had a clean audit trail. Every action an agent took, recorded. Who did it, what it touched, and when it ran. I was proud of it. Then I watched an agent remove a status enum from a database column. Not a value I wanted changed. It was a status that other records depended on. The audit trail caught it. Logged the delete, the actor, the timestamp. Caught it perfectly, and caught it after the status was already gone.&lt;/p&gt;

&lt;p&gt;That’s the moment the “governance gap” stopped being a phrase I’d read in other people’s posts and became something I’d built by accident. I had governance you could read. I didn’t have governance that could say no.&lt;/p&gt;

&lt;p&gt;This is what I learned about that gap by building a tool to close it. The tool is &lt;a href="https://www.tacoda.dev/open-refinery/" rel="noopener noreferrer"&gt;open-refinery&lt;/a&gt;, a control plane that governs how AI-driven work reaches repos, models, and tools. The lesson is simpler than the tool. The gap everyone’s naming isn’t a missing policy. The gap is that nothing makes an agent respect it. &lt;em&gt;And a policy nothing enforces is a suggestion.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The gap is between the policy and the work
&lt;/h3&gt;

&lt;p&gt;People use “governance gap” to name the space between the policy you wrote and the agent doing the work. You have a policy. The agent runs. The policy and the run never meet, so nothing makes the agent respect it.&lt;/p&gt;

&lt;p&gt;The common fix is to add something. A policy document, an approval step, or a content filter. Each of these is a thing you write down. &lt;em&gt;None&lt;/em&gt; of them is a thing that stops an action.&lt;/p&gt;

&lt;p&gt;Here’s the distinction that took me three prototypes to see. An audit log observes: it tells you what happened. Governance has to &lt;strong&gt;gate&lt;/strong&gt;. It must stop the thing &lt;em&gt;before&lt;/em&gt; it happens. My prototype with the clean audit trail was all observation. When the agent deleted that status, nothing stood between the intent and the delete. The log wrote the event down a moment too late to matter.&lt;/p&gt;

&lt;p&gt;Once you see governance as gating instead of recording, the question changes. It’s no longer “what policy should we write.” It’s “where does the policy get enforced before the action does.” That question has a location in it. Most governance talk doesn’t.&lt;/p&gt;

&lt;h3&gt;
  
  
  A gate has to live somewhere
&lt;/h3&gt;

&lt;p&gt;A gate is a control that runs before an action and can refuse it. To run before the action, it has to sit between the thing that wants to act and the thing being acted on. It needs a seam. In most agent setups, nobody drew the seam.&lt;/p&gt;

&lt;p&gt;So the first real decision in open-refinery was to split two things that usually run together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The harness does the work&lt;/strong&gt;. It’s the agents, the prompts, the memory, the tools, the orchestration. It lives in your application. It decides what to try.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The platform governs how work reaches a target&lt;/strong&gt;  — the repos, the models, the tools the harness wants to touch. The platform does none of the work. open-refinery executes nothing. It sits on the seam and decides whether an action reaches its target.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fehqbt46ih04f3ghgpi7w.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fehqbt46ih04f3ghgpi7w.png" width="800" height="276"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The harness never touches the target directly. Every action passes a gate that can say no, and the platform owns that gate.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Draw that line and the gate has a home. The harness asks to do something. The platform checks the policy. The action reaches the target, or it doesn’t. Skip the line and your policy floats free of the work. That was the prototype I started with: the policy lived in one place, the actions happened in another, and nothing connected them.&lt;/p&gt;

&lt;p&gt;None of enforce-before-execute is new. RBAC, IAM, and Kubernetes admission controllers are all examples. What’s new is who’s on the other side of the gate. A service either has permission or it doesn’t. An agent reads your policy in prose and decides how to apply it. The boundary is what lets you stop trusting that interpretation: the policy gets enforced on the seam, not left to the agent to honor.&lt;/p&gt;

&lt;p&gt;The boundary is the whole idea. Everything else here is a consequence of it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make the loop boring on purpose
&lt;/h3&gt;

&lt;p&gt;I wanted the platform to run without a human watching. Lights-out. I also wanted every action fully auditable. Those two goals fight each other the moment the system makes a choice you can’t explain.&lt;/p&gt;

&lt;p&gt;You can’t audit “the model decided to.” You can audit a function that takes recorded inputs and returns a recorded output. So the core loops in open-refinery are plain code, not agent choices. The policy is encoded there, deterministically, where the agent can’t reinterpret it away. There are two loops: transition, which moves a work item from one stage to the next, and produce, which runs an action against a target. The agent fills in the inputs. The code decides what happens.&lt;/p&gt;

&lt;p&gt;This audits the mechanism, not the judgment. Which input the agent picks is still the agent’s call. Determinism doesn’t make the decision safe. It makes the decision inspectable, and it puts a gate between the decision and its effect.&lt;/p&gt;

&lt;p&gt;Every action runs the same sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;authorize -&amp;gt; enforce policy -&amp;gt; execute -&amp;gt; record
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The order is important. Authorize before the run, or you can’t prevent it. Record after the run succeeds, or a crash mid-action loses the event.&lt;/p&gt;

&lt;p&gt;The check itself is small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;decision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;enforce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;strict&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;denied&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;PolicyDenied&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A policy names who may do what to which resource. In strict mode, nothing’s allowed unless a policy allows it. In audit mode, everything’s allowed unless a policy denies it. Strict mode &lt;em&gt;fails closed&lt;/em&gt;: if nothing explicitly allows an action, it’s denied. Audit mode &lt;em&gt;fails open&lt;/em&gt;: an unknown action runs. Which one you pick is the whole difference between a gate and a log. Audit mode is where you learn what your agents do. Strict mode is where you decide what they may do. That enum from the top of the post? A denied action under a strict policy on the status resource. One line, and it never happens.&lt;/p&gt;

&lt;h3&gt;
  
  
  Human oversight is a dial, not a switch
&lt;/h3&gt;

&lt;p&gt;One of the things I got wrong early was treating human-in-the-loop as on or off. Either a person approved every action, which didn’t scale, or nobody did, which was the prototype that deleted the status. Both are bad. The useful setting sits between them, and it should be a setting.&lt;/p&gt;

&lt;p&gt;open-refinery has five oversight levels, set per process. At L0, manual, every action needs a human to approve it. L1 and L2 loosen the grip: assisted, then supervised, where only the gated steps wait for a person. L3 is autonomous, no approval. L4 is dark, fully lights-out. You pick the level for a process based on how much you trust it — not for the whole system at once.&lt;/p&gt;

&lt;p&gt;What stays the same across all five is the machinery. The gates run the same way at every level, and the records share one schema. What changes is how many approval events the trail carries, and how many actions wait for a person before they execute. Because the loop is deterministic, moving the dial doesn’t change what gets checked or how it’s recorded; only who has to say yes, and how often.&lt;/p&gt;

&lt;p&gt;The boundary shows up again here. The dial sits on the platform, not in the harness. You can turn a process from supervised to dark without touching a line of agent code, because the approval step lives on the seam.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to tell whether a policy is real
&lt;/h3&gt;

&lt;p&gt;Once the platform could gate actions, I needed to know which policies were actually doing anything. A policy you wrote and a policy an agent respects are not the same policy.&lt;/p&gt;

&lt;p&gt;open-refinery models this with claims. A governance claim is a statement about a repo: agents may not delete production data, all migrations get reviewed. Each claim can be backed two ways. An instruction states the intent. A gate enforces it. A claim is covered &lt;em&gt;only&lt;/em&gt; when it has both.&lt;/p&gt;

&lt;p&gt;That gives three states, and the middle one is the whole problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Instruction and gate:&lt;/strong&gt; covered. The policy is written, and something stops violations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Instruction, no gate:&lt;/strong&gt; partial. You wrote the policy. Nothing enforces it. This is the governance gap, made countable. It was the most common state I found. It’s the one that feels safe and isn’t.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Neither instruction nor gate:&lt;/strong&gt; an imitation surface. It reads as governed. It isn’t.&lt;/p&gt;

&lt;p&gt;open-refinery scores a repo on this. How many claims have a gate, and not just an instruction? I won’t pretend I have clean fleet-wide numbers to wave at you. What the model buys me is the ability to ask the question per repo instead of assuming the answer. Partial coverage isn’t a failure of intent. It’s a gap you can name, which means you can close it.&lt;/p&gt;

&lt;h3&gt;
  
  
  A record you can’t quietly rewrite
&lt;/h3&gt;

&lt;p&gt;Gating decides what happens. Provenance decides whether you can trust the record of what happened. These are different jobs, and I discovered that after ran them together at first.&lt;/p&gt;

&lt;p&gt;An audit log you can edit isn’t evidence. If someone can delete the record of the delete, the record is worth nothing. So the records in open-refinery are immutable, and each one chains to the one before it with a hash. Change a past event and every hash after it breaks. One function walks the chain and checks it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;verify_chain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# raises if any link's hash doesn't match
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This doesn’t stop someone from doing something bad. It stops them from hiding that they did. I want to be honest about which one this is: &lt;em&gt;tamper-evidence&lt;/em&gt;, not tamper-proofing. It only holds if two things are true: someone actually checks the chain, and its head is anchored somewhere the writer can’t reach. Put the log and the verifier on the same box an attacker owns, and they can rewrite an event and recompute every hash after it. A local chain raises the cost of quiet tampering. It doesn’t make it impossible.&lt;/p&gt;

&lt;p&gt;The gate isn’t one thing in one place, either. It shows up at three seams. The transition gate checks a work item moving from one stage to the next. The invoke gate checks an executor about to run a command. And there’s a call the harness makes before it acts at all:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /authorize { role, action, resource, intent: "deploy to prod" }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The harness asks before it deploys. It gets back an allow, or a PolicyDenied. The policy does nothing until it sits on one of these seams. A policy with no seam is the partial-coverage case from the last section, seen from the code side.&lt;/p&gt;

&lt;h3&gt;
  
  
  The policy itself needs governing
&lt;/h3&gt;

&lt;p&gt;This is the part the governance-gap conversation skips. If governance lives on the boundary, then changing the boundary is itself an action. So who governs that?&lt;/p&gt;

&lt;p&gt;In my early prototypes, the policy lived in a config file. Anyone who could edit the file could change it, and there was no record of who changed what, or why. I’d built a system that gated every agent action and left the policy itself wide open. That’s not a small hole. That’s the same gap, just moved up one level.&lt;/p&gt;

&lt;p&gt;In open-refinery, a policy change is a governed action like any other. It walks an approval chain. It lands in the same audit trail. Every version of a policy records its author, its timestamp, and what changed. Ask “who changed this policy, and when, and why,” and there’s an answer.&lt;/p&gt;

&lt;p&gt;A policy file anyone can edit with no record isn’t governance. It’s a suggestion with a version history, at best. Governance that can’t govern itself doesn’t hold. This is the second-order problem, and it’s where the boundary earns its keep: moving the seam is an action, so it passes a gate too.&lt;/p&gt;

&lt;h3&gt;
  
  
  Put one gate on one policy this week
&lt;/h3&gt;

&lt;p&gt;You don’t need a platform to start closing your own governance gap. You need to stop treating your audit log as a control.&lt;/p&gt;

&lt;p&gt;Pick one policy you’ve written for your agents. A real one: in a doc, a prompt, or a README. Find where it’s enforced. If the honest answer is “it isn’t,” you’ve found a partial-coverage surface on your own system. That’s the gap, and now you can see it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add one gate:&lt;/strong&gt; a check that runs before the action and can refuse it. Start with the action you’d least want an agent to take without you watching. For me that was a destructive database change. Pick yours.&lt;/p&gt;

&lt;p&gt;Make the policy change leave a trace. Even a commit with a reason in the message is a start on second-order governance. The goal is that “who changed this policy” always has an answer.&lt;/p&gt;

&lt;p&gt;Do those three things and you’ve moved from governance you can read to governance that can say no. That’s the whole gap, and it closes one gate at a time.&lt;/p&gt;

</description>
      <category>aigovernance</category>
      <category>agenticai</category>
      <category>governance</category>
      <category>darkfactories</category>
    </item>
    <item>
      <title>Charter and Harness: What Each One Is</title>
      <dc:creator>Ian Johnson</dc:creator>
      <pubDate>Tue, 30 Jun 2026 23:47:29 +0000</pubDate>
      <link>https://dev.to/tacoda/charter-and-harness-what-each-one-is-3l60</link>
      <guid>https://dev.to/tacoda/charter-and-harness-what-each-one-is-3l60</guid>
      <description>&lt;p&gt;It seems that every time I &lt;em&gt;think&lt;/em&gt; I know the shape of the harness, a new fact presents itself that I have to account for. I recently wrote a book &lt;a href="https://leanpub.com/harness-engineering" rel="noopener noreferrer"&gt;Harness Engineering&lt;/a&gt;. But new information has caused me to rethink my model a bit. I plan to leave the term &lt;em&gt;harness&lt;/em&gt; alone, and tighten up the definition. In addition, I add the term &lt;em&gt;charter&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;What follows is a chapter I’m working on for the book to bridge the gap in my model. I’m offering it completely free because I think it’s the best definition I’ve had so far. Your feedback is very welcome.&lt;/p&gt;

&lt;p&gt;Clone a repository that someone has tuned for agentic coding and open the .claude folder at its root. What you find is mostly prose. A CLAUDE.md that explains the project. Rule files with names like testing.md and migrations.md. A few skills, each a short markdown file describing a workflow. Maybe a settings.json wiring up a hook. Almost none of it is code that runs on its own. It is a stack of instructions, written for a reader who happens to be a model.&lt;/p&gt;

&lt;p&gt;Now start the agent: Claude Code, or whatever the team standardized on. It reads that folder, holds it in mind, and goes to work on your repo. The folder did nothing. The agent did. There, in a single clone, sit the two things this book is about: the material you author, and the engine that acts on it. This chapter names both and says what each is made of. The next chapter draws the line between them. Here the job is to fill the two boxes.&lt;/p&gt;

&lt;h3&gt;
  
  
  The charter: what you author
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;charter&lt;/strong&gt; is everything you author to specify how the agent should behave in your project. That .claude folder is most of it. The word is borrowed from its plainest sense: a charter is a founding document that says how a thing is to be governed. Your repo’s charter governs the agent that enters it. You write it, you review it in pull requests, you amend it when it turns out wrong, and it governs every agent that works the repo until you change it.&lt;/p&gt;

&lt;p&gt;A charter comes in four kinds of material, and naming them now saves arguments later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Guides&lt;/strong&gt; instruct before the agent acts: the CLAUDE.md, the rule files, the slash commands, the prose half of a skill. A guide is a sentence the agent reads and weighs against the situation, like &lt;em&gt;prefer composition over inheritance, and justify a base class in the PR description&lt;/em&gt;. No graph expresses that. It is judgment, written down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool definitions&lt;/strong&gt; declare what the agent can reach for and how it is wired: the mcp.json entry, a tool’s schema, a skill’s script, or a plugin’s config. One artifact can span families: a skill’s prose is a guide, but its script a tool definition. The definition is yours and lives in the repo. The thing it points at, the installed server or the plugin binary, is external, and the next chapter sorts that seam.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sensors&lt;/strong&gt; tell the agent whether the work it just did is acceptable: the test files, the lint config, the type-checker settings. You authored them, so like a hook they are charter even though they run. A sensor reports; it does not refuse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gates&lt;/strong&gt; refuse: the pre-commit hook, the branch-protection rule, the blocking reviewer. A gate is a sensor wired to a stop.&lt;/p&gt;

&lt;p&gt;Guides, tool definitions, sensors, gates. If something in your charter is none of those four, it is probably not earning its place.&lt;/p&gt;

&lt;p&gt;Most of a charter is prose, and that is the point. A rule written as a sentence covers cases no control-flow graph would, because the agent weighs it instead of branching on it. Some of a charter is code you author, a hook or a gate, and that code is still charter: you wrote it to state a standard, so it is a specification the engine runs, never the engine itself. A charter is also a living thing. It is versioned in the repo, reviewed like any change, and amended as the project teaches you where it was wrong. The rule you write today because an agent ignored a convention is a line in a document you will keep editing for as long as the repo lives.&lt;/p&gt;

&lt;p&gt;Here is the whole of it, as a definition you can hold: the charter is your repo’s .claude folder, all of it (the rules, the hooks, the agents, the skills, the slash commands, the settings), plus the dependencies you declare: plugins, policy packs, shared skills, tools such as MCP servers. Those are two questions about a thing, not one. The families above sort by &lt;em&gt;role&lt;/em&gt;: is it a guide, a tool definition, a sensor, a gate? &lt;em&gt;Origin&lt;/em&gt; is the second question: did you write it by hand in the folder, or adopt it as a declared dependency? A declared dependency keeps its declaration in your repo, which is charter, while its installed body lives outside, which is not. Declaration in, source out. Either way the test does not change: you authored or adopted it to specify behavior.&lt;/p&gt;

&lt;p&gt;If you have been in this field a while, you may have called this folder “the harness.” It is the charter. The word slipped for an understandable reason, and the next chapter traces it; for now, hold the line that the folder you author is the charter, and the engine that reads it is the harness.&lt;/p&gt;

&lt;h3&gt;
  
  
  The harness: the engine that applies it
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;harness&lt;/strong&gt; is the engine that applies the charter against the model. The field has a shorthand worth borrowing: an agent is a model plus a harness. The model predicts text and nothing more. The harness is everything wrapped around it to turn prediction into work, and the charter is what the harness consults while it does. So the relationship runs in a line. You author a charter; the harness applies it to drive the model; the model produces a result, a diff or a document or an answer. Charter, harness, model, result. The charter is the input you control. The harness is the only stage that acts. The next chapter takes that line apart; here it is enough to see the shape.&lt;/p&gt;

&lt;p&gt;An engine that does this has jobs, and Part B of this book takes them one at a time. It assembles the context the model sees each turn, choosing what to load and what to drop, and invokes the tools your charter defined. The permissions you set decide which files it may touch and which commands it may run. The gates fire, the sensors report, and the loop turns with bounds and a kill switch until the work is done or the budget is spent. Every one of those is the engine at work, not a standard you wrote down. The standard is the charter; the doing is the harness.&lt;/p&gt;

&lt;p&gt;You get most of a harness rather than build it. Claude Code is one, Cursor is another, and a LangGraph orchestration your platform team stood up is a third. The vendor ships the engine, you point it at your repo, and it reads your charter on the way in. For most teams that is the entire relationship with the harness: choose one, configure it, move on. The teams that build their own, a custom orchestrator or a queue that runs agents across a fleet, are doing harness engineering proper, and Part B of this book is written for them. Part A is for everyone, because everyone writes a charter.&lt;/p&gt;

&lt;p&gt;A harness can even ship its own charter-like pieces: Claude Code’s built-in slash commands, a default skill, baked-in rules. Those are harness, because the vendor authored them, not you, and they do not sit in your .claude folder. Your charter can override them. Drop a fix-bug skill into your folder and the agent runs your bug workflow instead of the vendor’s default; write a rule that bends a built-in command to your process, and your version wins. The built-in is harness, your override is charter, and the agent meets the charter.&lt;/p&gt;

&lt;p&gt;One part of the charter couples to the harness more tightly than the rest: &lt;em&gt;workflow&lt;/em&gt;. A rule or a hook is a primitive the harness simply reads, but how work gets defined, prioritized, and completed is a process the harness has to register and run, not merely read. That is one reason a team builds its own harness, when the workflow it needs is one a stock harness cannot express. Part A’s chapter on contracts is where you define the workflow; Part B’s on orchestration is where the harness runs it.&lt;/p&gt;

&lt;p&gt;Two words, two boxes: the charter you author, the harness that applies it. They are not the same kind of thing, and it is a mistake to treat them as one. Where exactly the line falls, why a hook you wrote is charter even though it runs, why the orchestrator is harness even though you might own it, is the next chapter. For now, hold the two boxes apart and know which one takes most of your time. For most of this book, it is the charter.&lt;/p&gt;

&lt;h3&gt;
  
  
  What’s in each box
&lt;/h3&gt;

&lt;p&gt;Run this against your own repo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your charter&lt;/strong&gt;  — everything you authored or adopted to specify behavior. Most of it is the whole .claude folder; the rest is declared dependencies. By role:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;guides: CLAUDE.md, rules, skills, slash commands (what instructs)&lt;/li&gt;
&lt;li&gt;tool and agent definitions: mcp.json, tool schemas, sub-agent and plugin config (what the agent reaches for)&lt;/li&gt;
&lt;li&gt;sensors: test files, lint and type-check config (what reports)&lt;/li&gt;
&lt;li&gt;gates: pre-commit hooks, branch protection, required reviews (what refuses)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By origin: written by hand in the folder, or a declared dependency (plugin, policy pack, shared skill, tool such as an MCP server) whose declaration is charter and whose installed body is external.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your harness&lt;/strong&gt;  — the engine you run, mostly not yours to write:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the coding agent (Claude Code, Cursor, Aider)&lt;/li&gt;
&lt;li&gt;any framework or orchestrator above it (LangGraph, a custom runner)&lt;/li&gt;
&lt;li&gt;the loop that runs work on repeat&lt;/li&gt;
&lt;li&gt;the runtime that fires your hooks, invokes your tools, and enforces your permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A thing on the first list, you maintain, and it is where your reliability comes from. A thing on the second, you mostly choose. Telling which list something belongs to is the whole skill the next chapter sharpens into a single test.&lt;/p&gt;

&lt;p&gt;The charter is the half of the work with your name on it. The rest of this book is about writing it well.&lt;/p&gt;

&lt;h4&gt;
  
  
  Additional Resources
&lt;/h4&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnficjpsgjfgjvdz2rooz.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnficjpsgjfgjvdz2rooz.png" width="569" height="628"&gt;&lt;/a&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj86ipzaittyvg8tr5j67.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj86ipzaittyvg8tr5j67.png" width="626" height="517"&gt;&lt;/a&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm4epobixnqb96v9xna5i.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm4epobixnqb96v9xna5i.png" width="799" height="448"&gt;&lt;/a&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4pgztu7tr2ntu7p8ghzg.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4pgztu7tr2ntu7p8ghzg.png" width="586" height="702"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>harnessengineering</category>
      <category>charterengineering</category>
      <category>agenticai</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Harness Engineering: Building Reliable Workflows Around Non-Deterministic Agents</title>
      <dc:creator>Ian Johnson</dc:creator>
      <pubDate>Fri, 26 Jun 2026 21:27:58 +0000</pubDate>
      <link>https://dev.to/tacoda/harness-engineering-building-reliable-workflows-around-non-deterministic-agents-4gh0</link>
      <guid>https://dev.to/tacoda/harness-engineering-building-reliable-workflows-around-non-deterministic-agents-4gh0</guid>
      <description>&lt;p&gt;Hello everyone. I just released a book about harness engineering. It’s on LeanPub. Pay what you want, starts at $10.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://leanpub.com/harness-engineering" rel="noopener noreferrer"&gt;https://leanpub.com/harness-engineering&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What does it cover?
&lt;/h3&gt;

&lt;p&gt;The discipline, the taxonomy, the behavioral system, the reliability layers, rules, guides, corpus, playbooks, rule cascades, architecture as communication, tools, skills, agents, plugins, MCP, evals, delivery orchestration, continuous delivery with agents, PR as checkpoints, policy libraries for team and org extension, patterns and anti-patterns, harness debt, post-mortem, harness curation, loop engineering. Everything I have learned over the past six months with hands-on experiments with a real production codebase.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why so cheap?
&lt;/h3&gt;

&lt;p&gt;We have a problem right now with junior and mid level engineers feeling the pressure of AI. I think we have an obligation to help them — and I think that means providing a path to senior. How could they get to senior if the agent does everything for them?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;We must give juniors the support they need to become seniors.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;One of the goals with this book is to support these folks in their growth by showing the big picture, and then anchoring it with cited works of software engineering canon. This book gives both and has an appendix specifically for juniors with how to operate and grow in the world of agentic coding.&lt;/p&gt;

&lt;p&gt;If you are concerned about losing your job or not being able to find a job doing software engineering because of LLMs, then this book is for you!&lt;/p&gt;

&lt;h3&gt;
  
  
  There’s more
&lt;/h3&gt;

&lt;p&gt;The book also covers scopes from single contributor to enterprise and provides actionable advice whether you read a particular part or the whole thing. It offers benefits to anyone looking to learn how to get better results out of their coding agents, regardless of experience, role, project, team, company, or organization.&lt;/p&gt;

&lt;p&gt;I hope it serves you well.&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>harnessengineering</category>
      <category>books</category>
      <category>agenticworkflow</category>
    </item>
    <item>
      <title>This just rearranged my view on harness engineering</title>
      <dc:creator>Ian Johnson</dc:creator>
      <pubDate>Thu, 25 Jun 2026 23:16:14 +0000</pubDate>
      <link>https://dev.to/tacoda/this-just-rearranged-my-view-on-harness-engineering-5ggh</link>
      <guid>https://dev.to/tacoda/this-just-rearranged-my-view-on-harness-engineering-5ggh</guid>
      <description>&lt;p&gt;I just read this article — I recommend you do as well.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.linkedin.com/pulse/loop-engineering-just-software-we-have-name-mike-piccolo-yb73c/" rel="noopener noreferrer"&gt;Loop Engineering Is Just Software Engineering. We Have a Name for That.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’ve been doing a lot of work on harnesses, but I have been focusing at the project level. I’ve only recently started to delve into LangChain/LangGraph. I built my own daemon that implements this with a shell on my local machine. It works, but it could be better. I had always thought of these as two layers and they’ve become crystal clear to me now.&lt;/p&gt;

&lt;p&gt;When I read this, I immediately want to try it, but I do not have access to an API key yet. Also, I don’t really want to clobber my organization with API pricing. So I asked claude to build a prototype in Python given the description. I tested it, and it worked pretty well.&lt;/p&gt;

&lt;p&gt;But if you’re already at the API point (and you probably are if you are looping), here’s the tool: &lt;a href="https://iii.dev/" rel="noopener noreferrer"&gt;https://iii.dev/&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  This idea is important
&lt;/h3&gt;

&lt;p&gt;The single most important takeaway from this article is that we’ve already solve the problems of loop engineering with distributed systems. Instead of getting stuck in loops or overpaying, you can take advantage of the queueing pattern — dead letter queues, circuit breakers, retries, and so on.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This idea is dynamite.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It had slipped past me because I was focused on the project level of the harness and loop engineering has been mainly focusing on creating loops with agents, not &lt;em&gt;how&lt;/em&gt; to do it. So diagrams and explanations tend to lead to synchronous or daemon sorts of solutions. In hindsight, it seems so obvious.&lt;/p&gt;

&lt;p&gt;It also removes the orchestrator problem: the fact that different subagents about bound to a main orchestrator thread replicates Brooks’ warning for organizations in the form of agents. But with the distributed system, there is no orchestrator — that’s been reduced to state. Brilliant!&lt;/p&gt;

&lt;h3&gt;
  
  
  Vocabulary
&lt;/h3&gt;

&lt;p&gt;It also shifts my vocabulary. I’m currently writing a book about harness engineering: &lt;a href="https://leanpub.com/harness-engineering" rel="noopener noreferrer"&gt;https://leanpub.com/harness-engineering&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I was focusing on the repository level, but now the delivery harness (as I was calling it) builds one level up. This will probably cause me to have to re-arrange my definitions, but here’s where my head is at.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layers of the Harness
&lt;/h3&gt;

&lt;p&gt;I visualize the harness like an onion. But the harness I’ve been talking about has been &lt;em&gt;code&lt;/em&gt;, and the delivery harness is a &lt;em&gt;service&lt;/em&gt;. That means that personal still sits at the center, wrapped by the project (or repo) harness, and then wrapped with team and org harnesses that implement policies just like software libraries, and then wrapped in the delivery or orchestration harness. I’ve used some tools like this recently, like Hivemind (&lt;a href="https://cohen-liel.github.io/hivemind/" rel="noopener noreferrer"&gt;https://cohen-liel.github.io/hivemind/&lt;/a&gt;). It’s great, and there are plenty of others to try, but this has changed my entire outlook on the type of architecture the delivery harness should use. I think he’s right — it’s a distributed system.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fifld59p282yd9gttkl7j.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fifld59p282yd9gttkl7j.png" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Thank you Mike Piccolo!
&lt;/h3&gt;

</description>
      <category>agenticworkflow</category>
      <category>agenticai</category>
      <category>distributedsystems</category>
      <category>harnessengineering</category>
    </item>
    <item>
      <title>Reading the Agent Log Like a Detective</title>
      <dc:creator>Ian Johnson</dc:creator>
      <pubDate>Wed, 24 Jun 2026 16:21:53 +0000</pubDate>
      <link>https://dev.to/tacoda/reading-the-agent-log-like-a-detective-dgb</link>
      <guid>https://dev.to/tacoda/reading-the-agent-log-like-a-detective-dgb</guid>
      <description>&lt;p&gt;The agent shipped a broken migration last month. The PR was clean, the tests passed locally, the reviewer signed off in twelve minutes, and the deploy to staging green-lit on the first try. Production started erroring within four minutes of the merge because the migration assumed a column existed that had been renamed two weeks earlier.&lt;/p&gt;

&lt;p&gt;The first instinct was to blame the model. &lt;em&gt;Bad agent, didn’t read the docs.&lt;/em&gt; The second instinct was to add a rule. &lt;em&gt;Always check the current schema before writing migrations.&lt;/em&gt; Both instincts felt productive. Neither would have fixed it.&lt;/p&gt;

&lt;p&gt;What fixed it was reading the transcript. The agent had, in fact, looked at the schema file. The schema file in the repo was correct. It had been updated when the column was renamed. But the agent had also loaded a database/CLAUDE.md rule that described the column under its &lt;em&gt;old&lt;/em&gt; name as the canonical reference. The rule was stale. The agent followed the rule, but the rule was wrong.&lt;/p&gt;

&lt;p&gt;You could call this a model problem — the agent trusted a stale rule over the live schema file sitting right next to it. &lt;strong&gt;But that’s not the lever you can pull.&lt;/strong&gt; The fixable cause was in the harness (the rules, skills, hooks, and tools wrapped around the model) and here it was a rule that &lt;em&gt;lied&lt;/em&gt;. It was visible in the log if you read it forensically.&lt;/p&gt;

&lt;p&gt;That’s what this post is about. Reading agent transcripts as evidence, not as narrative. The five failure modes that show up over and over. And the habit of treating every wrong commit as a case to investigate before reaching for a fix.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replay, not blame
&lt;/h3&gt;

&lt;p&gt;The first move when an agent ships bad code is to resist the urge to fix and move on. The fix is downstream. The question worth asking is &lt;em&gt;how did the agent decide to do that&lt;/em&gt;. The answer lives in the transcript.&lt;/p&gt;

&lt;p&gt;Most modern agent harnesses save full transcripts somewhere. Claude Code stores them in ~/.claude/projects//, Cursor keeps recent ones in its UI, and most CI-driven agent runs log to a build artifact. The transcript is your scene of the incident. It records what the agent loaded, what it read, what it ran, what it produced, and in what order. Everything you need to reconstruct its reasoning is in that file.&lt;/p&gt;

&lt;p&gt;The discipline is to treat the transcript the same way an SRE treats a stack trace: not as a list of complaints, but as the most reliable evidence of what happened. The agent’s text answers in the transcript are sometimes hand-wavy. The tool calls are not. The tool calls are facts.&lt;/p&gt;

&lt;p&gt;Read the tool calls first.&lt;/p&gt;

&lt;h3&gt;
  
  
  The five failure modes
&lt;/h3&gt;

&lt;p&gt;Across the agent transcripts I’ve reviewed, the same five failure modes keep producing wrong-code incidents. Each one has a cause in the model but each also has a cause in the harness, and the harness is the half you can change. All five are visible in the log.&lt;/p&gt;

&lt;h4&gt;
  
  
  Missing context
&lt;/h4&gt;

&lt;p&gt;The agent never read the file that mattered. It wrote a migration without checking the schema. It wrote a test without checking the test conventions. It added a new module without checking how other modules in the same area were structured.&lt;/p&gt;

&lt;p&gt;How to spot it in the log: scroll back to the start of the task and look at the Read and Glob tool calls. Note which files the agent loaded. Compare to the files the change touched. The pattern is: &lt;em&gt;the agent edited file X, but never read file Y, where Y was the file that defined the convention X had to match&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The fix isn’t in the model. The fix is to make sure the relevant context loads automatically. Either by giving the rule a sharper path scope (so it loads when the agent touches the area), by adding a skill that walks the agent through the right files in order, or by adding a hook that injects the relevant context at the start of tasks of that shape.&lt;/p&gt;

&lt;p&gt;The agent did what it was told to do. The instruction set didn’t include &lt;em&gt;read Y first&lt;/em&gt;. That’s a harness gap.&lt;/p&gt;

&lt;h4&gt;
  
  
  Contradicted rules
&lt;/h4&gt;

&lt;p&gt;The agent followed a rule that was wrong. The rule was outdated, or it referenced a pattern the codebase no longer uses, or two rules in the harness said different things and the agent picked the one that produced the bug.&lt;/p&gt;

&lt;p&gt;How to spot it in the log: look at the rules the agent cited in its reasoning. If the rule cited matches the code change exactly, but the change is still wrong, the rule is the bug. Cross-check the rule’s claims against the current code. &lt;em&gt;Rule says use UserRepository, code uses UserService&lt;/em&gt; is a smoking gun.&lt;/p&gt;

&lt;p&gt;The fix is the rule-rot audit. Rules drift faster than people think. A rule written six months ago can be confidently citing a class that was renamed in April. The agent trusts the harness; if the harness lies, the agent ships lies.&lt;/p&gt;

&lt;p&gt;The contradicted-rules failure mode is the one I find most embarrassing because the agent did exactly what the harness said to do. The agent isn’t wrong. The harness is wrong. And the harness was wrong because I didn’t audit it.&lt;/p&gt;

&lt;h4&gt;
  
  
  Hallucinated APIs
&lt;/h4&gt;

&lt;p&gt;The agent called a function that doesn’t exist. Used an option that isn’t supported. Imported from a module that doesn’t export what it imported.&lt;/p&gt;

&lt;p&gt;How to spot it in the log: search the agent’s output for function names and option names, then grep the actual codebase or library for them. The mismatches are obvious. The agent might have hallucinated findOrFailWithLock when the real method is findOrFail and there’s no lock option at all.&lt;/p&gt;

&lt;p&gt;The fix has two parts. Short-term, tighten the harness with documentation links or type signatures pointing at the real reference is the cheapest cure. Longer-term, give the agent tools instead of documentation. An MCP server that exposes the library’s actual API (so the agent can &lt;em&gt;call&lt;/em&gt; it instead of guessing) ends the hallucination class. A type checker run as a sensor (an automated check wired into the harness that fails the run when something’s wrong) catches the hallucinations that slip through.&lt;/p&gt;

&lt;p&gt;The recurrence pattern worth noting: hallucinations cluster around APIs the model is confidently familiar with from training but where the project has chosen to use the library differently than the common convention. The model isn’t making up the function from nothing; it’s substituting the public-API version when the project actually wraps it.&lt;/p&gt;

&lt;h4&gt;
  
  
  Scope creep
&lt;/h4&gt;

&lt;p&gt;The agent changed more than it was asked to. The task was &lt;em&gt;fix a bug in the login flow&lt;/em&gt;. The diff also reformatted three unrelated files, renamed a helper for clarity, and updated four imports because the rename touched them. The bug fix is correct. The reformatting is harmless. But the PR is now many times the size it should be, and the reviewer’s eye is going to slide past the actual fix.&lt;/p&gt;

&lt;p&gt;How to spot it in the log: look at the Edit and Write tool calls. Compare the files touched against the files the task description called out. The gap is the creep.&lt;/p&gt;

&lt;p&gt;The fix is a rule and a sensor. The rule says &lt;em&gt;touch only what you must, clean up only your own mess&lt;/em&gt;. The sensor is a pre-PR check that fails if the diff is wider than a budget; N files, M lines, whatever your numbers happen to be.&lt;/p&gt;

&lt;p&gt;Scope creep is one of the most common failure modes in my experience and the most invisible. It doesn’t break anything. It just makes every PR worse to review and every regression harder to track down.&lt;/p&gt;

&lt;h4&gt;
  
  
  Premature commit
&lt;/h4&gt;

&lt;p&gt;The agent committed before the work was done. The tests pass because the agent skipped writing the failing test first. The acceptance criteria look met because the agent declared them met without verifying. The PR description claims feature X is implemented when, under the hood, feature X is a stub.&lt;/p&gt;

&lt;p&gt;How to spot it in the log: look for the moment the agent decided the work was done. Compare against the actual evidence of doneness. Did the agent run the test it claimed proves the fix? Did the test exist before the change, or did the agent write it after seeing the change pass? Did the agent verify the acceptance criteria, or did it skip the verification step and write the PR description from memory of what the task asked?&lt;/p&gt;

&lt;p&gt;The fix is procedural. A skill called finishing-work that walks the agent through the verification steps before allowing a commit. Perhaps a pre-commit hook that fails if the changed files don’t have at least one paired test change. An agent that has to produce a &lt;em&gt;completion report&lt;/em&gt; (see my earlier post on &lt;a href="https://blog.tacoda.dev/post-mortems-for-agent-runs-2cceeaf13f2e" rel="noopener noreferrer"&gt;post-mortems for agent runs&lt;/a&gt;) before declaring done.&lt;/p&gt;

&lt;p&gt;Premature commit is the failure mode where the agent looks most productive and is least useful. The PR ships, the work isn’t finished, and the next person to touch the code finds out.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reading the log: a worked example
&lt;/h3&gt;

&lt;p&gt;Here’s a transcript fragment from a real run I investigated. The task was to add a new field to a user profile.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; User: Add a `pronouns` field to the user profile. Optional string, nullable.
[Read .claude/CLAUDE.md]
[Read app/Models/User.php]
[Glob database/migrations/*_users_*.php]
[Read database/migrations/2024_05_12_add_email_to_users.php]
[Write database/migrations/2026_06_18_add_pronouns_to_users.php]
[Edit app/Models/User.php] // added 'pronouns' to $fillable
[Bash: php artisan migrate - pretend]
[Edit app/Http/Resources/UserResource.php] // added pronouns to toArray
[Bash: vendor/bin/pest tests/Feature/UserResourceTest.php]
[Write app/Http/Controllers/UserController.php] // added validation rule
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The PR shipped. The reviewer signed off. Production threw an error the next morning, from the admin panel: Undefined array key 'pronouns'. The admin user view read $user['pronouns'] straight off the resource array, and the resource never put it there.&lt;/p&gt;

&lt;p&gt;Reading the transcript: the agent loaded the project’s CLAUDE.md, looked at the User model, looked at a recent migration as a template, and wrote a similar migration. It also added the field to the User resource and the controller validation. It did &lt;em&gt;not&lt;/em&gt; read app/Http/Resources/AdminUserResource.php, which the admin panel uses and which lists its fields explicitly, key by key. Unlike the model’s $fillable, the admin resource doesn’t auto-pick up new columns. A field exists there only if someone names it. The agent never opened the file, so pronouns was never added, and the admin view that assumed the key blew up.&lt;/p&gt;

&lt;p&gt;The failure mode: &lt;em&gt;missing context&lt;/em&gt;. The harness had no rule that said &lt;em&gt;user-profile fields appear in three places: User, UserResource, AdminUserResource&lt;/em&gt;. The agent followed the convention from the two places it knew about and shipped a partial change.&lt;/p&gt;

&lt;p&gt;The fix wasn’t &lt;em&gt;be smarter, agent&lt;/em&gt;. The fix was a path-scoped rule in app/Http/Resources/CLAUDE.md that named the resource files that needed to stay in sync. The rule went in. The sensor (a quick AST check that any new $fillable field appears in both resource files) went in alongside. The next pronouns-shaped task didn’t repeat the bug.&lt;/p&gt;

&lt;p&gt;That’s the loop. The transcript pointed at the gap and the harness change closed the gap. The model wasn’t the variable.&lt;/p&gt;

&lt;h3&gt;
  
  
  What forensic reading teaches you about your harness
&lt;/h3&gt;

&lt;p&gt;The payoff of reading transcripts isn’t fixing the specific bug. It’s the patterns that show up after twenty or thirty of them.&lt;/p&gt;

&lt;p&gt;You start noticing which files the agent rarely reads, and asking why. You notice which rules it cites a lot (they’re load-bearing) and which it never cites (they’re dead weight). You notice the directories where transcripts go wrong most often — usually the directories with the worst seams, the least context, or the most conventions that aren’t written down.&lt;/p&gt;

&lt;p&gt;There’s a cost the failure modes hide from each other. The fix for missing context is almost always a new rule and every rule you add is a future candidate for contradicted-rules rot. Fix enough gaps by piling on instructions and you build the exact stale-harness problem that produced failure mode two. So the audit cuts both ways: you read transcripts to find the rule you’re missing, and you read them to find the rule that’s now lying. A harness isn’t a pile of rules that only grows. The transcripts tell you which rules to add and which to retire, and the second list matters as much as the first. The transcript is the highest-resolution feedback signal the harness produces.&lt;/p&gt;

&lt;p&gt;Two practical reading habits help.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read at least one failed transcript a week.&lt;/strong&gt; Not all of them. One. Pick the one that produced the most surprising failure. Read it end to end. Note the failure mode. Ask whether the harness has a gap there. The discipline isn’t to fix everything. Rather, it’s to develop the muscle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep a transcript notebook.&lt;/strong&gt; A file in the repo. Each entry: link or paste of the transcript, the failure mode (pick from the five above), the gap in the harness, the change that fixed it. After ten entries, the notebook becomes a map of where your harness is weak.&lt;/p&gt;

&lt;p&gt;The notebook doesn’t have to be pretty. It has to exist.&lt;/p&gt;

&lt;h3&gt;
  
  
  The thing the model isn’t responsible for
&lt;/h3&gt;

&lt;p&gt;A theme runs through every failure mode above. You can argue each one back to &lt;em&gt;“the model should have known better”&lt;/em&gt;. But arguing about whose fault it is doesn’t change the rate. Changing the harness does. Missing context is a closable harness gap. Contradicted rules are stale rules. Hallucinated APIs are weak documentation or missing tools. Scope creep is an unset constraint. Premature commit is a missing procedure.&lt;/p&gt;

&lt;p&gt;This isn’t a defense of the model. The model is fallible, and the next generation of models will be more capable than this one. The point is that &lt;em&gt;the cheap improvements live in the harness, not in the model&lt;/em&gt;. You can’t retrain the model from your laptop. You can fix a rule, scope a rule, add a sensor, sharpen a tool, and write a procedure. All five are at your fingertips. All five reduce the rate of the same bug shipping again.&lt;/p&gt;

&lt;p&gt;Forensic reading of transcripts is the practice that tells you which one to fix.&lt;/p&gt;

&lt;h3&gt;
  
  
  Read one transcript this week
&lt;/h3&gt;

&lt;p&gt;Find the last commit your agent made that you wish it hadn’t, or the last PR that came back with a “this isn’t quite right” review. Open the transcript that produced it.&lt;/p&gt;

&lt;p&gt;Read the tool calls first. Note which files the agent loaded and which it should have loaded. Find the rule it cited or skipped. Cross-check the agent’s claims against the actual code. Pick which of the five failure modes you’re looking at.&lt;/p&gt;

&lt;p&gt;Then write the harness change that would have stopped it. Whether a new rule, a path-scope, a sensor, a skill or a tool. Ship it the same day.&lt;/p&gt;

&lt;p&gt;After three or four passes, you’ll start seeing the same failure mode keep recurring. That’s the signal. The pattern is the rule waiting to be written. The transcript is just the place the pattern lives until you spot it.&lt;/p&gt;

&lt;p&gt;The model can’t read its own transcript. You can.&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>agenticai</category>
      <category>postmortem</category>
      <category>investigation</category>
    </item>
    <item>
      <title>What Breaks When You Skip the Harness</title>
      <dc:creator>Ian Johnson</dc:creator>
      <pubDate>Tue, 23 Jun 2026 14:47:49 +0000</pubDate>
      <link>https://dev.to/tacoda/what-breaks-when-you-skip-the-harness-3237</link>
      <guid>https://dev.to/tacoda/what-breaks-when-you-skip-the-harness-3237</guid>
      <description>&lt;p&gt;The agent kept running the tests, watching them go red, scrolling up to find the failure, and then running the tests again because it had already lost the output. Third time in one session. The model wasn’t the problem. The session’s memory of its own output was. I had Claude tee the test command to a log file. After that it read the log instead of re-running.&lt;/p&gt;

&lt;p&gt;That was one fix on one project. The pattern repeats. If the model keeps producing bad code, what is actually broken?&lt;/p&gt;

&lt;h3&gt;
  
  
  The model isn’t the problem
&lt;/h3&gt;

&lt;p&gt;Output quality is the sum of two things: the model and the surroundings.&lt;/p&gt;

&lt;p&gt;In the teams I’ve worked with, the obsession is always the model. They upgrade. Switch tools. New IDE plugin every month. They read every benchmark. The defects keep landing in the same places.&lt;/p&gt;

&lt;p&gt;The surroundings get almost no attention. Those surroundings are the harness: the files, rules, tools, and feedback loops that wrap the model inside a real project. A CLAUDE.md with conventions. Skills — codified workflows the agent can run by name. MCP servers. Scoping rules. A feedback.md that records what the team has corrected. Pre-commit gates.&lt;/p&gt;

&lt;p&gt;Without those, the model is guessing.&lt;/p&gt;

&lt;p&gt;Five things teams blame on the model, each pointing at a missing piece of the harness, each with a first move you can ship this week. Some of the bigger fixes (codifying a team’s lore, writing a useful project-specific MCP, etc.) are quarter-of-work to do properly. The first move is week-sized. The first move gets you started.&lt;/p&gt;

&lt;h3&gt;
  
  
  APIs that don’t exist
&lt;/h3&gt;

&lt;p&gt;The model writes a call to client.users.list({ since: lastSyncAt }). The argument doesn’t exist. The real API takes a Unix timestamp on a different endpoint. Code compiles. Test fails. The engineer reads the docs. The bug gets filed as “the model hallucinated.”&lt;/p&gt;

&lt;p&gt;Except the model didn’t hallucinate from nothing. It wrote what it learned from training data and didn’t verify. Nothing in the session told it to.&lt;/p&gt;

&lt;p&gt;The fix is grounding. Give the model a way to read the real docs in the moment.&lt;/p&gt;

&lt;p&gt;Two grounding moves do the work.&lt;/p&gt;

&lt;p&gt;First: an MCP server for library docs — Context7 is one. The model fetches current docs and writes against what’s there. When it fails, it’s almost always because the rule telling the agent to fetch first wasn’t in CLAUDE.md.&lt;/p&gt;

&lt;p&gt;Second: a project-specific MCP for internal APIs. Every team I’ve worked with has one or two services nobody got around to documenting well. An MCP that serves the OpenAPI spec — or a search over the service’s source — puts real signatures in front of the model.&lt;/p&gt;

&lt;p&gt;A single rule in CLAUDE.md ties it together. “Fetch docs before writing library code.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Smallest first move:&lt;/strong&gt; add Context7. Add the rule. Try it on the next library task.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code that doesn’t match yours
&lt;/h3&gt;

&lt;p&gt;The model writes a new service. The file lands in the wrong directory. Errors throw instead of returning. The naming is camelCase in a snake_case codebase. The diff looks foreign.&lt;/p&gt;

&lt;p&gt;The reviewer sees it and rewrites it. The next PR has the same problems.&lt;/p&gt;

&lt;p&gt;The model has no reference for “how we do it here.” The rules live in three engineers’ heads and one out-of-date README.&lt;/p&gt;

&lt;p&gt;Part one is a CLAUDE.md that names the project’s conventions in plain words. Where files go. How errors propagate. Naming style. Testing framework. Logger. Time library. One or two sentences each. The bare minimum.&lt;/p&gt;

&lt;p&gt;Part two matters more: &lt;em&gt;worked examples.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When we moved a service to hexagonal architecture, the agent kept writing new code on the wrong side of the ports-and-adapters boundary. The old code was still everywhere and gave it permission to keep doing what it had always done.&lt;/p&gt;

&lt;p&gt;The model copies texture from examples better than from prose. A skill or a feature doc with one full worked example , such as a real PR, a real file, or a real test, gives it something to imitate. Three examples beat a thousand words of rules.&lt;/p&gt;

&lt;p&gt;The catch: if the codebase itself is inconsistent (legacy module is snake_case, new code is camelCase), the model picks up the contradiction and writes both. The worked example has to come from the side of the codebase you want the agent to copy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Smallest first move:&lt;/strong&gt; pick the workflow your team runs most often. Write one skill for it. Drop one full example into the file.&lt;/p&gt;

&lt;h3&gt;
  
  
  The same bug, in every session
&lt;/h3&gt;

&lt;p&gt;You correct the agent on Monday. It used the wrong logger. You tell it to use the project’s logger. It does. Friday, new task, wrong logger again.&lt;/p&gt;

&lt;p&gt;The agent didn’t learn. The session ended. The correction went with it. The fix is a feedback loop the agent can read. Not a chat log, but a file.&lt;/p&gt;

&lt;p&gt;Create a feedback.md. When the team corrects the agent on something that’s a rule, not a typo, write it down in three lines. Context. Mistake. Rule going forward. Tell the agent to read it before every relevant task.&lt;/p&gt;

&lt;p&gt;The first entry in mine was about flaky E2E tests. The team’s rule was fix them or delete them. Never live with them. The agent kept patching around them instead. After the third time I corrected it I stopped repeating myself and wrote the rule down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who writes to it:&lt;/strong&gt; anyone on the team. The cost of adding an entry has to be smaller than the cost of fixing the same thing twice. Otherwise nobody writes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The promotion rule:&lt;/strong&gt; when the same correction shows up in feedback.md three times, lift it into CLAUDE.md or into a skill. Three is arbitrary, pick a threshold and stick with it. I chose three because of the rule of three. The point is that nothing useful stays buried in the feedback file forever. Every promoted entry is one more rule the model reads at the start of the next session.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Smallest first move:&lt;/strong&gt; create the file. Write one entry today. Add one line to CLAUDE.md that says read it first.&lt;/p&gt;

&lt;h3&gt;
  
  
  The knowledge stays in heads
&lt;/h3&gt;

&lt;p&gt;A senior engineer is on a call, explaining why the payment service has its own retry logic that is different from the rest of the system. The new engineer nods. The agent in everyone’s terminal doesn’t, because the agent wasn’t on the call.&lt;/p&gt;

&lt;p&gt;Two weeks later the agent writes a new payment integration. It calls the shared retry helper. It has no idea there’s a reason not to. The PR ships. The on-call gets paged.&lt;/p&gt;

&lt;p&gt;This is tribal knowledge. The lore of the system. It lives in Slack scrollback, in senior engineers’ heads, in a Notion or Confluence page.&lt;/p&gt;

&lt;p&gt;The fix is codification. Three kinds of files do the work.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;glossary&lt;/strong&gt; captures domain vocabulary. The word “session” can mean three different things in one system — auth, user, analytics. The glossary picks one definition per term and points to it. One paragraph each.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;feature doc&lt;/strong&gt; holds the why of a part of the system. Why payments has its own retry logic. Why we picked Postgres over MySQL. Why the queue uses Redis Streams and not Kafka. Short notes a new reader can scan in a minute.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skills&lt;/strong&gt; hold workflows. The steps to ship a database migration. How we add a feature flag. The onboarding checklist for a new service joining the monitoring stack.&lt;/p&gt;

&lt;p&gt;The hard part of this fix is not the format. The hard part is getting the senior engineer to write the feature doc instead of explaining it on calls. That’s a social problem, not a documentation problem. Pair on the first one. Make the doc the deliverable.&lt;/p&gt;

&lt;p&gt;A billing module on one of my projects had quiet invariants about how transaction state combined. The agent made an arithmetic change that looked clean in the diff. It would have produced wrong totals in production. The reason the change mattered wasn’t in the code. It was in the team’s heads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Smallest first move:&lt;/strong&gt; pick the one piece of lore most often re-explained to new engineers. Write it down as a feature doc this week. Reference it in CLAUDE.md.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reviewers as the harness
&lt;/h3&gt;

&lt;p&gt;Every PR re-litigates the same things. Naming, test coverage, error handling, conventions, and so on. Same three reviewers, same six comments. PRs take two days to merge. The reviewers are tired.&lt;/p&gt;

&lt;p&gt;The reviewers have become the harness. Humans are doing what a script should do.&lt;/p&gt;

&lt;p&gt;The fix is gates the machine can run.&lt;/p&gt;

&lt;p&gt;A pre-commit hook catches the same six review comments before the diff exists. Match the gate to the team’s actual repeated comments. It could be naming-convention nits that needs a linter rule, missing-test-file nits that needs a coverage gate, or risky-file-changed nits that needs a CODEOWNERS-style script. The point is to read the team’s review history and turn the top three repeat offenders into machine checks.&lt;/p&gt;

&lt;p&gt;A review skill or review agent reads the diff and flags the things a linter can’t see.&lt;/p&gt;

&lt;p&gt;A code-health check (CodeScene is what I use) gives a numeric score for maintainability and blocks regressions. The score settles the argument over whether a diff makes the code worse in seconds.&lt;/p&gt;

&lt;p&gt;The worst case I’ve watched was naming inconsistencies during a migration. Two valid styles in the codebase, the model picking both, sometimes in the same PR. We added sensors that flagged the old style in new files and ran them in CI. The drift stopped showing up in review.&lt;/p&gt;

&lt;p&gt;The work reviewers should be doing (design, intent, what the PR is actually trying to do) gets crowded out by everything machines could catch and don’t.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Smallest first move:&lt;/strong&gt; pick the review comment your team has left most often this month. Write one pre-commit hook for it. Push it in a PR this week.&lt;/p&gt;

&lt;h3&gt;
  
  
  Write three rules this week
&lt;/h3&gt;

&lt;p&gt;The model isn’t the problem. The surroundings are the work.&lt;/p&gt;

&lt;p&gt;Three things to do this week.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open CLAUDE.md. Write three rules: the three things you’ve corrected an agent on this week. That’s the start of a harness.
&lt;/li&gt;
&lt;li&gt;Create feedback.md. Write one entry. Tell the agent to read it. The feedback loop now exists.
&lt;/li&gt;
&lt;li&gt;Pick one workflow you’ve run through an agent more than three times this month. Make it a skill. The third time you reach for it, it’s already written down.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The harness rots too. CLAUDE.md drifts as the project changes. Skills go stale. feedback.md swells to four hundred entries and turns into noise. Two skills contradict each other and the agent picks one at random. Most teams who tried a harness and quit do so at the rot stage, not the setup. Treat it like the code it wraps: audit on a schedule, delete what no longer applies, promote what has earned its place.&lt;/p&gt;

&lt;p&gt;This isn’t the only thing that matters. A great model still beats a mediocre one on hard problems. I don’t have a clean benchmark for the bigger claim. What I have is a preference: given the choice between a great model in a bad harness and a mediocre model in a good one, I take the mediocre model every time. The model’s mistakes I can debug. The drift I cannot.&lt;/p&gt;

&lt;p&gt;Watch for two weeks. The defects don’t vanish. Instead they show up earlier at pre-commit, in the feedback file, or in the review skill instead of in CI or production. That’s the win.&lt;/p&gt;

</description>
      <category>harnessengineering</category>
      <category>agenticworkflow</category>
      <category>agenticai</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Tools Are Harness Too</title>
      <dc:creator>Ian Johnson</dc:creator>
      <pubDate>Mon, 22 Jun 2026 15:30:29 +0000</pubDate>
      <link>https://dev.to/tacoda/tools-are-harness-too-3l9k</link>
      <guid>https://dev.to/tacoda/tools-are-harness-too-3l9k</guid>
      <description>&lt;p&gt;I had a rule in my project CLAUDE.md for months that said &lt;em&gt;always check Code Health before committing&lt;/em&gt;. The agent followed it about 70% of the time. The other 30%, it forgot; usually on the longer tasks, when its context window was filling up and the rules at the start of the prompt were losing weight against the code at the end. I added emphasis. The 70% became 80%. I added a section break. 85%. I rewrote the rule in capital letters with three exclamation points. Same 85%.&lt;/p&gt;

&lt;p&gt;Then I gave the agent a &lt;a href="https://codescene.com/product/code-health" rel="noopener noreferrer"&gt;Code Health&lt;/a&gt; tool. Not a rule. A tool — an MCP server entry point called code_health_safeguard that returned a structured analysis of the staged diff. The next day, the agent ran it before every commit. The day after that. The day after that. The compliance went to 100% and stayed there.&lt;/p&gt;

&lt;p&gt;The difference between a rule and a tool is the difference between &lt;em&gt;telling the agent what to do&lt;/em&gt; and &lt;em&gt;making the right thing easier than the wrong thing&lt;/em&gt;. Both belong in a working harness. Tools are the half nobody talks about.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rules tell, tools enforce
&lt;/h3&gt;

&lt;p&gt;A rule is a paragraph. The agent reads it, weighs it against the other things in its context, and produces output. If the rule wins the weighing, the agent follows it. If something else wins (the code’s existing pattern, a more recently mentioned constraint, the user’s apparent intent in the prompt) the rule loses.&lt;/p&gt;

&lt;p&gt;The probability the rule wins is not 100%. It is some number less than 100%, and the number drops as the context grows. Rules in long-running agent sessions are like introductory remarks at a long meeting: present, recorded, mostly forgotten by hour three.&lt;/p&gt;

&lt;p&gt;A tool is different. A tool is an &lt;em&gt;affordance:&lt;/em&gt; a thing the agent can do, exposed at the same level as everything else the agent does. The agent’s working surface is &lt;em&gt;I can read files, edit files, run shell commands, and call these specific functions&lt;/em&gt;. A tool slots into that list. The agent doesn’t have to weigh a paragraph against another paragraph; it sees a function and decides whether to call it.&lt;/p&gt;

&lt;p&gt;The decision shifts from &lt;em&gt;should I obey this rule&lt;/em&gt; to &lt;em&gt;should I use this capability&lt;/em&gt;. The shift matters because the decision happens at a different layer in the agent’s reasoning. Capability decisions are cheaper than rule-weighing decisions. They survive the long-context drift.&lt;/p&gt;

&lt;h3&gt;
  
  
  When a tool beats a rule
&lt;/h3&gt;

&lt;p&gt;Not every rule should become a tool. The translation has a cost. Tools take time to write, they introduce new surface area to maintain, and they only earn their place when they replace a rule that fails often enough to be worth replacing.&lt;/p&gt;

&lt;p&gt;A rule belongs as a tool when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The rule produces a check the agent can run.&lt;/strong&gt; &lt;em&gt;Always verify the schema before writing a migration&lt;/em&gt; is a checkable property. &lt;em&gt;Be thoughtful about naming&lt;/em&gt; is not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The check has a deterministic output.&lt;/strong&gt; &lt;em&gt;Run the linter and pass&lt;/em&gt; is a yes-or-no answer. &lt;em&gt;Match the team’s style&lt;/em&gt; is a vibes call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The check fails silently when skipped.&lt;/strong&gt; A rule the agent can ignore without anyone noticing is a rule that begs to become a tool. The tool is the gate that closes the silent skip.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The check is run often.&lt;/strong&gt; A check the agent should run on every commit is a tool. A check the agent should run on the rare cross-project migration is a skill or a rule.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For everything else, rules are still the right unit. Tone, naming conventions, design preferences, when-to-use-X-vs-Y judgment calls: those are paragraphs, not function calls. Trying to encode them as tools is the path to a brittle harness with thirty checks and no taste.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Code Health example, end to end
&lt;/h3&gt;

&lt;p&gt;To make the trade-off concrete, here’s the rule-to-tool conversion that started this post.&lt;/p&gt;

&lt;p&gt;The rule, in CLAUDE.md:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## Code Health is authoritative

Before suggesting a commit, run a Code Health check on the changed files.
If any file regresses below 9.0, refactor before committing. Do not commit
code with a Code Health below the project floor.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule is fine. It’s specific, it names the threshold, it tells the agent what to do. It just doesn’t fire reliably under load.&lt;/p&gt;

&lt;p&gt;The tool is the CodeScene MCP server’s pre_commit_code_health_safeguard. It exposes itself to the agent as a callable function. The function takes the staged diff and returns a structured response — files checked, scores before and after, regressions flagged, suggested refactorings. The agent doesn’t have to remember to do the right thing; the right thing is one tool call away, and &lt;em&gt;not&lt;/em&gt; calling it now looks conspicuous next to the call to git diff --cached that the agent makes anyway.&lt;/p&gt;

&lt;p&gt;The rule still lives in CLAUDE.md, with one important change. It now says &lt;em&gt;call&lt;/em&gt; &lt;em&gt;pre_commit_code_health_safeguard before suggesting a commit&lt;/em&gt;. The rule names the tool. The tool does the work. The rule is shorter and the agent’s compliance is higher.&lt;/p&gt;

&lt;p&gt;That’s the conversion shape: a rule that names a tool, and a tool that does the check the rule used to describe. Both layers are present. Each is cheaper than it would be alone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tools as constraint design
&lt;/h3&gt;

&lt;p&gt;The other reason tools matter is that they let you design constraints that rules can’t express.&lt;/p&gt;

&lt;p&gt;Consider the constraint &lt;em&gt;the agent should never delete production data&lt;/em&gt;. As a rule, it’s a sentence: &lt;em&gt;don’t run destructive operations against production&lt;/em&gt;. The rule is correct and entirely advisory. As a tool, it’s the inverse. The absence of any production-database-write capability in the tool list at all. The agent literally cannot call what isn’t exposed.&lt;/p&gt;

&lt;p&gt;This is the difference between &lt;em&gt;advisory constraint&lt;/em&gt; and &lt;em&gt;constraint by construction&lt;/em&gt;. Rules are advisory. Tools are construction. The harness designer chooses where on that spectrum to put each constraint.&lt;/p&gt;

&lt;p&gt;The rule of thumb I use: constraints that are critical to safety belong as constraints by construction. Tools the agent shouldn’t use shouldn’t be in the tool list. Permissions the agent shouldn’t have shouldn’t be granted. The advisory layer (rules) is for guidance; the construction layer (tools, permissions, capabilities) is for safety.&lt;/p&gt;

&lt;p&gt;When I’m reading a harness, the first place I look isn’t the rules. It’s the tool list and the permission allowlist. The tool list tells me what the agent &lt;em&gt;can&lt;/em&gt; do. The rules tell me what it &lt;em&gt;should&lt;/em&gt; do. The safety properties live in the first answer, not the second.&lt;/p&gt;

&lt;h3&gt;
  
  
  What an MCP server gives you
&lt;/h3&gt;

&lt;p&gt;MCP (the Model Context Protocol) is the standard most modern agents use to expose tools. The mechanics are simple: a server runs somewhere (often as a subprocess of the editor), declares a set of capabilities to the agent, and handles requests when the agent calls them.&lt;/p&gt;

&lt;p&gt;The capabilities are arbitrary. They can wrap a CLI, an HTTP API, a database, a local file system, a third-party service, or pure computation. Whatever can be wrapped becomes a tool.&lt;/p&gt;

&lt;p&gt;A few of the MCP servers I find load-bearing in my own work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CodeScene.&lt;/strong&gt; Code Health checks, refactoring guidance, hotspot detection. Replaces a long rule about complexity with a tool that gives concrete numbers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context7.&lt;/strong&gt; Real-time library documentation. Replaces the failure mode where the agent hallucinates an API by giving it a way to look up the actual one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The project’s own MCP server.&lt;/strong&gt; Custom checks the team cares about. A “show me the current schema” tool. A “validate this against the API spec” tool. A “list the existing repositories” tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The third one is the one most teams skip and shouldn’t. The project’s own MCP server is where the team’s domain constraints get encoded as capabilities. A validate_contract tool, a verify_acceptance_criteria tool, a check_naming_convention tool — each takes a rule that used to live in CLAUDE.md and turns it into something callable.&lt;/p&gt;

&lt;h3&gt;
  
  
  The tradeoff: tools cost more
&lt;/h3&gt;

&lt;p&gt;The honest cost of tools is that they’re harder to write than rules. A rule is a paragraph in a markdown file. A tool is a function with a typed schema, error handling, an installer, and a test surface. Writing a good tool takes an afternoon. Writing the rule it replaces takes ten minutes.&lt;/p&gt;

&lt;p&gt;The afternoon pays back, but only for the right tools. The rules to convert are the ones with high cost (the rule has been violated multiple times, each violation has a real cost) and high check-ability (the check is mechanical). The rules to leave alone are the ones with low cost or low check-ability. They’re cheaper to keep as rules and live with the occasional miss.&lt;/p&gt;

&lt;p&gt;There’s a second tradeoff. Tools accumulate. Every tool added to the agent’s surface is another option it considers. A tool list that grows to fifty capabilities slows the agent’s decision-making and pushes the relevant tools further out of immediate reach. Tools, like rules, deserve their own audit cadence in the same way rule-rot eats at rules, tool sprawl eats at agents.&lt;/p&gt;

&lt;p&gt;Keep the load-bearing tools. Delete the ones the agent never calls. Watch the same pattern as the rules: which tools earn their slot, which sit unused.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tools as the third layer of the harness
&lt;/h3&gt;

&lt;p&gt;If rules are the first layer and sensors are the second, tools are the third. Each layer does work the others can’t.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rules&lt;/strong&gt; communicate intent and shape the agent’s reasoning. They’re cheap. They’re advisory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sensors&lt;/strong&gt; detect when intent isn’t followed. They’re checks at the workflow’s edges. They’re load-bearing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tools&lt;/strong&gt; make right things easy and wrong things impossible. They’re capabilities. They’re construction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A working harness uses all three. The rule says &lt;em&gt;do X&lt;/em&gt;. The tool makes &lt;em&gt;X&lt;/em&gt; a callable capability. The sensor verifies &lt;em&gt;X&lt;/em&gt; happened. Each layer has a different cost and a different power. The mistake is to load everything into one layer because that layer is comfortable.&lt;/p&gt;

&lt;p&gt;The teams I see with rules-only harnesses ship the same bugs over and over with great-sounding documentation. The teams I see with tools-only harnesses ship code that compiles but doesn’t match the team’s conventions. The teams I see thriving have a balance — rules for the guidance, tools for the capability, sensors for the gate.&lt;/p&gt;

&lt;h3&gt;
  
  
  The seams between rules and tools
&lt;/h3&gt;

&lt;p&gt;The most powerful arrangement is when a rule references a tool by name. The rule loads into context, communicates the intent, and tells the agent which tool to reach for. The tool, when called, produces the deterministic answer the rule was hand-waving toward.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## Verifying schema changes

Before writing a migration that adds, drops, or alters a column, call
`db_schema_dump` to get the current schema. Confirm the columns you reference
exist and are typed as you expect. Cite the schema dump in your reasoning so
the reviewer can see you verified.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s a rule paired with a tool. The rule does what rules do: it explains the intent, names the situations it applies to, and tells the agent how to handle them. The tool does what tools do: it provides the actual capability — fetching the current schema, in a structured form, deterministically.&lt;/p&gt;

&lt;p&gt;The pair is stronger than either piece alone. The rule alone would be ignored on long contexts. The tool alone wouldn’t know when to be called. Together, they cover the case.&lt;/p&gt;

&lt;h3&gt;
  
  
  Convert one rule to a tool this week
&lt;/h3&gt;

&lt;p&gt;Pick a rule from your CLAUDE.md that names a check. &lt;em&gt;Always run the linter. Verify the schema first. Check the test coverage.&lt;/em&gt; Anything that says &lt;em&gt;always X&lt;/em&gt; where &lt;em&gt;X&lt;/em&gt; is mechanical.&lt;/p&gt;

&lt;p&gt;Look at whether there’s an MCP server that already does &lt;em&gt;X&lt;/em&gt;. If so, install it, expose the tool to the agent, and rewrite the rule to name the tool. If not, look at the size of &lt;em&gt;X&lt;/em&gt;. If it’s small (a shell command, a script), write a one-tool MCP server that exposes the script. Most agent harnesses make this surprisingly easy now.&lt;/p&gt;

&lt;p&gt;Ship the tool. Update the rule to name the tool. Watch the next ten agent runs. The rule that was complied with 80% of the time becomes 100% almost overnight, because the agent isn’t being asked to remember anymore. The capability is right there.&lt;/p&gt;

&lt;p&gt;Rules tell. Tools enforce. The harness with both is the harness the agent doesn’t drift out of.&lt;/p&gt;

</description>
      <category>agenticai</category>
      <category>aitools</category>
      <category>harnessengineering</category>
      <category>agenticworkflow</category>
    </item>
    <item>
      <title>Harness Engineering — Building Reliable Workflows Around Non-Deterministic Agents</title>
      <dc:creator>Ian Johnson</dc:creator>
      <pubDate>Sun, 21 Jun 2026 22:55:28 +0000</pubDate>
      <link>https://dev.to/tacoda/harness-engineering-building-reliable-workflows-around-non-deterministic-agents-3f5m</link>
      <guid>https://dev.to/tacoda/harness-engineering-building-reliable-workflows-around-non-deterministic-agents-3f5m</guid>
      <description>&lt;p&gt;Six months ago this was a blog post. Then a second blog post. Then Bridle, Sellier, intent-driven-delivery, and eventually Keystone.&lt;/p&gt;

&lt;p&gt;Now it’s a book: &lt;em&gt;Harness Engineering — Building Reliable Workflows Around Non-Deterministic Agents.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;What surprised me writing it: the experiments earned their place. Bridle taught me the flywheel. Sellier taught me defaults beat flexibility. IDD taught me the team layer can be reified. Keystone is the synthesis they pointed at.&lt;/p&gt;

&lt;p&gt;It’s on LeanPub now. Pay what you want, starts at $10.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://leanpub.com/harness-engineering" rel="noopener noreferrer"&gt;https://leanpub.com/harness-engineering&lt;/a&gt;&lt;/p&gt;

</description>
      <category>agenticworkflow</category>
      <category>books</category>
      <category>softwareengineering</category>
      <category>harnessengineering</category>
    </item>
    <item>
      <title>Sensors: The Other Half of the Harness</title>
      <dc:creator>Ian Johnson</dc:creator>
      <pubDate>Sat, 20 Jun 2026 15:15:50 +0000</pubDate>
      <link>https://dev.to/tacoda/sensors-the-other-half-of-the-harness-3mml</link>
      <guid>https://dev.to/tacoda/sensors-the-other-half-of-the-harness-3mml</guid>
      <description>&lt;p&gt;The pre-commit hook caught a migration last Tuesday that would have shipped to staging green and broken production on the next deploy. The migration dropped a foreign key constraint without naming a backfill plan, and the rule that said &lt;em&gt;every constraint change needs a backfill plan&lt;/em&gt; had been sitting in migrations.md for four months. The rule didn’t stop the agent. The rule didn’t stop the human reviewer either. The diff was 38 files and the constraint drop was a single line. What stopped it was a four-line shell script wired to pre-commit that grepped the staged migration for DROP CONSTRAINT and exited non-zero if no--backfill: comment followed it.&lt;/p&gt;

&lt;p&gt;That four-line script is what I want to talk about. Most of what gets written about agent harnesses is about rules — what CLAUDE.md should say, where to put it, how to scope it, when to version it. Rules are half the harness. The other half is the set of checks that fire when a rule gets broken. I call them sensors, and they get talked about a lot less than they should.&lt;/p&gt;

&lt;h3&gt;
  
  
  The asymmetry in the conversation
&lt;/h3&gt;

&lt;p&gt;Read any of the well-circulated posts on agent harnesses and count the words spent on each side. Rules are the centerpiece. Sensors are glanced over, as if they’re so obvious they don’t deserve their own treatment. They aren’t obvious, and the assumption that they’re already there is the most expensive assumption in the field.&lt;/p&gt;

&lt;p&gt;The asymmetry has a reason. Rules are easier to write. A rule is a paragraph. A sensor is a script, a hook, a CI step, a pre-commit config, a custom check. Rules sit at the cognitive level a writer naturally operates at; sensors sit at the level of plumbing. The first one is a lot more fun to write than the second.&lt;/p&gt;

&lt;p&gt;The asymmetry also has a cost. &lt;em&gt;Rules without sensors are vibes.&lt;/em&gt; The agent reads them, claims to follow them, and gets graded on whether the resulting code &lt;em&gt;looks&lt;/em&gt; like it followed them. &lt;em&gt;Looks like&lt;/em&gt; is the failure mode. The agent is a probabilistic system. It will obey a rule most of the time and skip it some of the time, and the times it skips are the times the rule mattered most: the awkward branch, the migration nobody likes to think about, or the edge case the rule was written to catch.&lt;/p&gt;

&lt;p&gt;A sensor changes the contract. The rule is no longer a hope. It’s a falsifiable check.&lt;/p&gt;

&lt;h3&gt;
  
  
  What a sensor actually is
&lt;/h3&gt;

&lt;p&gt;A sensor is anything that can detect whether a rule was followed and signal that detection in a way the workflow respects. The shape is narrow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It runs deterministically.&lt;/li&gt;
&lt;li&gt;It returns a clear pass or fail.&lt;/li&gt;
&lt;li&gt;It fires at a point in the workflow where its result still matters.&lt;/li&gt;
&lt;li&gt;It’s cheap enough that nobody is tempted to skip it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most of the sensors in a working harness aren’t fancy. A grep with set -e. A line in a linter config. A pytest fixture that asserts the database fixture hasn’t drifted. A pre-commit hook that runs mypy on changed files. A GitHub Actions job that fails the PR if the migration directory has a file without a paired rollback. None of these is impressive on its own. The point isn’t the individual sensor; &lt;em&gt;it’s the discipline of having one for every rule that matters.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Five places a sensor fires
&lt;/h3&gt;

&lt;p&gt;There are five workflow positions where sensors earn their pay. Each catches a different class of failure. A working harness has sensors at most of them.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pre-edit
&lt;/h4&gt;

&lt;p&gt;The earliest sensor is the one the agent encounters before it writes code. The rule is loaded into context; the sensor is something the agent can run to check its own work before producing output. A type stub generator, a schema dump, a --dry-run command that simulates the change. The agent doesn’t need approval; it just needs to see what its proposed change &lt;em&gt;would&lt;/em&gt; do.&lt;/p&gt;

&lt;p&gt;Pre-edit sensors are the rarest of the five, because they require giving the agent tools, not just rules. The pay-off is that they catch errors before any bytes hit disk. The agent that can run prisma validate on a proposed schema change is going to make fewer broken commits than the agent that can only read the rule that says &lt;em&gt;make sure your schema is valid&lt;/em&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pre-commit
&lt;/h4&gt;

&lt;p&gt;Pre-commit is where most teams put their first sensor, because Git makes it easy. A .pre-commit.config.yaml or a husky hook fires the moment the agent (or human) tries to land a change. The check has the staged diff to look at, the rest of the repo for context, and a hard exit code that the workflow respects.&lt;/p&gt;

&lt;p&gt;This is the sensor layer for rules that constrain the &lt;em&gt;artifact&lt;/em&gt; — what the code looks like, not what it does. Format, lint, type, dead code, banned imports, naming patterns, the migration backfill check above. Cheap, fast, local. The agent that breaks a pre-commit hook either fixes the violation or doesn’t commit. Either outcome beats &lt;em&gt;the agent commits and nobody notices&lt;/em&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pre-PR
&lt;/h4&gt;

&lt;p&gt;A pre-PR sensor is one that runs after the commits exist but before the diff goes up for review. The check has more to work with: a full branch, a base ref, a set of changed files that’s no longer one commit at a time. It’s the right layer for cross-file checks. &lt;em&gt;Did you change this API without updating its consumers? Did you add a new migration without bumping the schema version? Does the test coverage diff drop more than two points?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Pre-PR runs cheap on CI and slow locally. Most teams run it on the branch push, which catches the agent’s work right as it’s offering the work for human attention. That timing matters. A check that fires after the human has started reviewing is a check that’s been skipped.&lt;/p&gt;

&lt;h4&gt;
  
  
  Post-merge
&lt;/h4&gt;

&lt;p&gt;Post-merge sensors are the ones that run against main. They aren’t strictly part of the agent’s loop, but they’re part of the harness because they detect when the loop produced something that broke once integrated. Smoke tests, end-to-end suites, schema-diff jobs that compare staging to production, query-plan monitors that fire when a slow query joins the rotation.&lt;/p&gt;

&lt;p&gt;The instinct is to think of post-merge as &lt;em&gt;CI&lt;/em&gt; and not as part of the harness. That’s a mistake. The agent’s behavior is shaped by what fails &lt;em&gt;after&lt;/em&gt; it shipped. A post-merge sensor that catches a regression and gets traced back to a missing rule is the most valuable kind of sensor in the whole stack. It tells you something the inner sensors missed, and it tells you what to add upstream.&lt;/p&gt;

&lt;h4&gt;
  
  
  Drift
&lt;/h4&gt;

&lt;p&gt;The fifth and most-skipped sensor is the one that checks whether the rules themselves are still true. The codebase moves. The framework version bumps. The pattern the rule used to describe gets replaced by a new pattern. The rule is now wrong, and nobody notices because nothing fires.&lt;/p&gt;

&lt;p&gt;A drift sensor is the check that wakes up periodically and asks: does this rule still match reality? Sometimes that’s a literal grep for the pattern the rule references. Sometimes it’s a count of how many files in the codebase still match the rule’s example. Sometimes it’s a last-modified audit that flags any rule older than six months for review. The cadence is monthly or quarterly; the goal is to keep the harness honest as the code beneath it changes.&lt;/p&gt;

&lt;p&gt;I’ll write a whole post about rule-rot soon. The short version: &lt;em&gt;a drift sensor is the difference between a harness that’s six months old and helpful and a harness that’s six months old and lying.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The pairing rule
&lt;/h3&gt;

&lt;p&gt;If a rule matters, it has a sensor. That’s the principle. It sounds rigid, and it is, on purpose.&lt;/p&gt;

&lt;p&gt;The discipline works because it forces a real question every time a rule gets written: &lt;em&gt;how would I know if this got violated?&lt;/em&gt; If the answer is &lt;em&gt;I’d see it in code review&lt;/em&gt;, the rule is on probation. Code review is slow, expensive, and inconsistent. It is also the wrong layer for rules that produce mechanical violations.&lt;/p&gt;

&lt;p&gt;If the answer is &lt;em&gt;the linter would catch it&lt;/em&gt;, the rule probably doesn’t need to be a rule. &lt;strong&gt;The linter is the rule. Just configure the linter and skip the prose.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The interesting cases are the rules where the answer is &lt;em&gt;nothing currently checks for this, but I could write a check&lt;/em&gt;. Those are the rules that earn a sensor. The sensor doesn’t have to ship the same day the rule ships, but it should be on the list and have a date.&lt;/p&gt;

&lt;p&gt;The rules that aren’t worth a sensor are the rules that aren’t worth writing down. &lt;em&gt;Be thoughtful&lt;/em&gt; is not a rule. &lt;em&gt;Bullet points should be sentence fragments unless they form a list of full sentences&lt;/em&gt; is also not a rule, because nothing catches a violation and the cost of writing it down is more than the cost of fixing the rare slip in editing.&lt;/p&gt;

&lt;p&gt;A rule with no sensor is either too vague to matter or important enough that someone should pair it within the month. &lt;em&gt;Either way, the absence of a sensor is information.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What good sensors share
&lt;/h3&gt;

&lt;p&gt;The sensors that survive in production look alike. They share five traits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They run in seconds.&lt;/strong&gt; A pre-commit hook that takes 90 seconds gets bypassed within two weeks. A pre-PR check that takes 12 minutes gets the --skip-checks shortcut added to the team Slack. The check has to be fast enough that running it is cheaper than figuring out how to avoid running it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They produce clear messages.&lt;/strong&gt; A failed sensor that prints EXIT 1 is a failed sensor that’s about to get ignored. A failed sensor that prints migrations/20260620_drop_users_index.sql dropped an index without a paired backfill comment. Add a comment starting with --backfill: explaining the plan, or annotate the migrations with --no-backfill-needed:  is a sensor that teaches the agent (or the human) how to fix the violation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They run locally.&lt;/strong&gt; A check that only exists in CI is a check that the agent finds out about ten minutes after it pushed. &lt;em&gt;Running the same check locally before push closes the loop.&lt;/em&gt; The pre-commit framework and npm run check-style scripts both make this easy; the discipline is to make sure CI runs the &lt;em&gt;same&lt;/em&gt; scripts the developer can run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They cite the rule they enforce.&lt;/strong&gt; Every sensor message that earns its weight names the rule. *Failed: rule MIGRATIONS-04 (foreign-key changes require a backfill plan).* The agent reads the message; if the rule is wrong, the agent can find it and propose a change. The sensor is the gate, but the rule is the explanation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They fail loud and pass quiet.&lt;/strong&gt; A sensor that prints OK checked 47 migration files in 1.2s every commit is a sensor whose output everyone learns to ignore. Pass silently. Fail with a paragraph. Save the human’s attention for the cases that need it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where sensors live in the project
&lt;/h3&gt;

&lt;p&gt;A working sensor layout has four physical homes. Each holds the sensors for a different workflow position.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.husky/ # or .pre-commit-config.yaml
  pre-commit # fast, staged-files only
  commit-msg # commit message linting
.github/workflows/
  pr-checks.yml # pre-PR, runs on push
  post-merge.yml # post-merge, runs on main
.claude/
  hooks/ # agent-facing pre-edit checks
  sensors/ # one shell script per drift check
scripts/check-*.sh # the actual checks, invoked from above
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The shape isn’t sacred. &lt;em&gt;What matters is that each sensor has a single home and the workflow knows where to call it.&lt;/em&gt; The anti-pattern is the same check running in three places with slightly different logic and the team unable to remember which one is authoritative.&lt;/p&gt;

&lt;p&gt;The sensors themselves go in scripts/ (or wherever your project keeps tooling) and get called by whichever runner needs them. That separation means the same check can fire from pre-commit, from CI, and from a drift-audit cron without three copies. One script, three invocations.&lt;/p&gt;

&lt;p&gt;Personally, I prefer to wrap these scripts in make tasks. The benefit that I receive is that the agent always knows to use make. And that makes everything easier to find and more consistent. I use make in CI too. Usually, I work with docker and so this is a natural pair for me. &lt;em&gt;Isolate the environment with docker; isolate the commands with make.&lt;/em&gt; That makes it easier for the human &lt;em&gt;and&lt;/em&gt; the agent to work with the repository.&lt;/p&gt;

&lt;h3&gt;
  
  
  Two sensors worth writing this month
&lt;/h3&gt;

&lt;p&gt;The two highest-value sensors for a team starting from zero are both small.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The migration safeguard.&lt;/strong&gt; Whatever your migration tool is, the team’s hardest-to-catch bugs ship through it. A pre-commit script that knows the patterns your migrations are supposed to avoid — DROP COLUMN without paired backfill, ADD COLUMN NOT NULL without a default, schema-breaking renames without a two-phase plan — catches more production issues than any rule on its own. The agent will write migrations that look right and miss the constraint. The sensor sees what the human reviewer doesn’t.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The diff-budget gate.&lt;/strong&gt; A pre-PR check that fails if the diff exceeds N files or M lines. The number is yours to pick. I use 15 files and 400 lines, which roughly maps to a PR a human can review in one sitting. The rule that says &lt;em&gt;keep PRs small&lt;/em&gt; is exactly the kind of rule that gets nodded at and then drowned in the next month. The sensor that fails the build at file 16 is the rule with teeth.&lt;/p&gt;

&lt;p&gt;Both are under fifty lines of shell. Both pay for themselves the first week.&lt;/p&gt;

&lt;h3&gt;
  
  
  The anti-patterns to watch
&lt;/h3&gt;

&lt;p&gt;A few sensor failure modes recur often enough to name.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The unrunnable sensor.&lt;/strong&gt; It lives in CI, takes 14 minutes, and depends on a secret only the deploy bot has. Nobody on the team can run it locally. By the time it fires, the human reviewer has already mentally signed off. The fix: factor out the local-runnable piece and run &lt;em&gt;that&lt;/em&gt; on every commit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The sensor that always passes.&lt;/strong&gt; Someone wrote it months ago, the inputs changed, the assertions still hold, the failure case is no longer reachable. The sensor is a green light glued to the dashboard. The fix: every sensor needs at least one &lt;em&gt;known-bad&lt;/em&gt; fixture that the test suite runs against it to confirm it still detects the violation it was written for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The flaky sensor.&lt;/strong&gt; It fails sometimes and passes the rest. The team learns to rerun. Within a month, the rerun habit has spread to the other checks, and a real failure gets bypassed because it &lt;em&gt;looked&lt;/em&gt; flaky. The fix: &lt;strong&gt;a flaky sensor is broken.&lt;/strong&gt; Pull it from the gate, fix it, or delete it. Do not leave it firing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rule with no sensor.&lt;/strong&gt; The whole point of this post. A rule that lives in CLAUDE.md and has no check is a rule that gets followed when the agent feels like it. The fix is the pairing rule above: if the rule matters, pair it within a month.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The sensor with no rule.&lt;/strong&gt; Less common but worth naming. A check fires, a developer doesn’t know why, the rule it’s enforcing isn’t written down. The check is correct on its own terms but unteachable. The fix: cite the rule in the failure message. If you can’t, write the rule.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pair one rule to a sensor this week
&lt;/h3&gt;

&lt;p&gt;Pick one rule from your CLAUDE.md — the one you find yourself reminding the agent about most. Open a file. Write a check. Wire it to pre-commit. Ship it.&lt;/p&gt;

&lt;p&gt;The right rule for the exercise is one with a clear violation pattern. &lt;em&gt;Always use the&lt;/em&gt; &lt;em&gt;Result type for fallible operations&lt;/em&gt; is a fine candidate; you can grep for throw in the changed files and warn. &lt;em&gt;Match the existing code style&lt;/em&gt; is a poor candidate; the violation pattern is too diffuse for a small sensor to catch.&lt;/p&gt;

&lt;p&gt;When the sensor catches its first violation, two things happen. The rule becomes load-bearing. The agent now has to satisfy it instead of just acknowledge it. Additionally, you find out whether the rule was right. Sensors that fail a lot on changes that look reasonable in review are sensors enforcing rules that need to change. &lt;em&gt;The sensor is the feedback loop; the rule is the hypothesis.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Add a second one next month. By the end of the quarter your harness will feel different. Not because the rules are smarter, but because the rules can be checked.&lt;/p&gt;

&lt;p&gt;The rules are the part everyone writes. The sensors are the part that makes them true.&lt;/p&gt;

</description>
      <category>agenticai</category>
      <category>softwareengineering</category>
      <category>harnessengineering</category>
    </item>
    <item>
      <title>The Accidental Framework</title>
      <dc:creator>Ian Johnson</dc:creator>
      <pubDate>Fri, 19 Jun 2026 15:23:25 +0000</pubDate>
      <link>https://dev.to/tacoda/the-accidental-framework-40l5</link>
      <guid>https://dev.to/tacoda/the-accidental-framework-40l5</guid>
      <description>&lt;p&gt;The thing I built was a Go binary that copied markdown files into a repository. I called it an installer. The README called it an installer. The CLI verb was init. For the several months in early 2026, that framing held. Then I sat down to plan the 1.0 release, started typing, and could not write the next paragraph without using the word &lt;em&gt;framework&lt;/em&gt;. Not in the marketing-blurb sense. In the load-bearing sense: a small runtime, a set of named extension points, conventional file layouts, a resolver that picked a winner among competing sources, a lockfile, a migration runner, per-backend adapters. The thing I had been calling an installer for four months had been doing framework work the whole time, and I just had not noticed.&lt;/p&gt;

&lt;p&gt;This post is about that moment of recognition and what came after. It is about the refactor that turned an installer-shaped framework into a framework-shaped framework. The most surprising part was not the rewrite. It was how little of the &lt;em&gt;concepts&lt;/em&gt; had to change. The names were already right. The directories needed to move. The runtime needed a physical line down the middle between framework code and content. But the abstractions I had been using (guides, corpus, sensors, actions, playbooks, adapters) held up under the new framing without revision. The work I had done naming things over the previous months turned out to be the work that made the refactor cheap.&lt;/p&gt;

&lt;p&gt;I want to walk through how that happened, what tipped me off, and the one wrong turn the refactor almost made before backing out of it. The story has a tidy ending; the lesson does not, and I will get to that.&lt;/p&gt;

&lt;h3&gt;
  
  
  The shape was there before the word
&lt;/h3&gt;

&lt;p&gt;Keystone started as keystone init — a binary you ran in a fresh repo and it laid down a harness/ directory full of markdown. Rules for the agent. A small lifecycle. A few sensors that ran your existing lint and test commands. Nothing especially framework-ish. The 0.1 README said &lt;em&gt;installer&lt;/em&gt; four times.&lt;/p&gt;

&lt;p&gt;Then I started adding things. Each addition felt like a feature, not an architectural shift. The commit log reads like a feature log:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;0.2: install-time options. Pick your agent. Add a target.&lt;/li&gt;
&lt;li&gt;0.3: restructured the harness into corpus, guides, sensors, flywheels. Named taxonomy for the first time.&lt;/li&gt;
&lt;li&gt;0.4: a kind taxonomy on guides and sensors. Different kinds, different load behavior.&lt;/li&gt;
&lt;li&gt;0.5: a migrate command for forward-compatible upgrades. The harness had a schema now, and the schema had a version.&lt;/li&gt;
&lt;li&gt;0.10: policy plugins. Org-level rules that projects pulled in.&lt;/li&gt;
&lt;li&gt;0.11: the policy cascade. Org → Team → Project. Strict and non-strict layers.&lt;/li&gt;
&lt;li&gt;0.12: playbooks. Ordered chains of actions.&lt;/li&gt;
&lt;li&gt;0.13: sensors as a tier-aware policy kind.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Read that list once and it looks like product growth. Read it twice and the shape gets harder to miss. By 0.13 there was a runtime with conventions, plugins, a cascade, a lockfile, migrations, and per-agent rendering. The README still said &lt;em&gt;installer&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The thing about a shape forming under a name is that the name is the last thing to change. Each individual commit looks like a small feature; the architectural mass piles up underneath and nobody is watching the mass. I was not, anyway. I was writing the next feature.&lt;/p&gt;

&lt;p&gt;When I opened a fresh document called PLAN-10.md and tried to summarize where things stood, the first sentence I wrote was: &lt;em&gt;Convert Keystone from a harness installer with org policy plugins into a harness framework.&lt;/em&gt; I stared at that for a while. The conversion was not a conversion. &lt;strong&gt;It was an admission.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What an installer was pretending not to be
&lt;/h3&gt;

&lt;p&gt;Here is what makes the installer label dishonest in retrospect. An installer drops files. It is done. The user takes it from there. The framework that Keystone had become did all of this on top:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It defined named extension points.&lt;/strong&gt; Guides, corpus, sensors, actions, playbooks, adapters per agent. Each one had a path convention, a frontmatter contract, and a load behavior. That is an API, not a directory listing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It resolved conflicts across sources.&lt;/strong&gt; The same /.md could exist in the project, in a team plugin, in an org plugin. Exactly one won. The resolution order mattered, and the ordering rules were stable across versions. That is a runtime, not a copy step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It tracked drift.&lt;/strong&gt; A lockfile pinned per-source SHAs and per-file hashes. The binary would notice when plugin files had been edited under it. That is integrity enforcement, not file-laying.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It migrated old layouts forward.&lt;/strong&gt; keystone migrate would walk an installed harness, apply numbered transforms, and bring it to the current schema. That is a schema migrator, with all the implications of having a schema.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It rendered the same harness into multiple targets.&lt;/strong&gt; Claude Code’s CLAUDE.md shape, Codex’s AGENTS.md, Cursor’s .cursor/rules/. The adapter layer translated one source into many. That is a code generator, not a paste-once-and-done.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Take any one of those in isolation and you can argue it was a feature an installer happened to include. Take them together and the argument falls over. Installers do not carry adapter layers. They do not have lockfiles. They do not have migration runners. They certainly do not have a cascade resolver with strict and non-strict overrides.&lt;/p&gt;

&lt;p&gt;The give-away, when I went looking, was the layout of the source tree. Go files at the repo root next to a harness/ directory full of markdown that the binary embedded. No physical boundary. A single Go module mixing the runtime with the content the runtime shipped. If you asked me to point at “the framework” and “the content” in the 0.x tree, I would have had to do it with words, not directories. &lt;em&gt;That is a tell.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The vocabulary did the heavy lifting
&lt;/h3&gt;

&lt;p&gt;Here is the part of the story that surprised me most. I was bracing for a rewrite — the kind where you sit down with a blank internal/framework/ directory and start naming concepts from scratch, then translate the old code into them, then translate again because the first names were wrong. That is the usual cost of recognizing a shape late.&lt;/p&gt;

&lt;p&gt;It did not happen. The abstractions held.&lt;/p&gt;

&lt;p&gt;When I sat down to write the ports-and-adapters layer of the 1.0 plan, I had a table to draft. One row per port. For each port, a name, a path convention, an activation rule. I wrote it expecting to discover gaps. Here is what I found instead.&lt;/p&gt;

&lt;p&gt;Every port already had a name. Every name was already in use. Every path was already conventional. &lt;em&gt;Guide&lt;/em&gt; meant a rule loaded on every turn at guides//.md. &lt;em&gt;Corpus&lt;/em&gt; meant reasoning loaded on demand at corpus//.md. &lt;em&gt;Sensor&lt;/em&gt; meant an automated check at sensors/.md. &lt;em&gt;Action&lt;/em&gt; meant a single unit of lifecycle work. &lt;em&gt;Playbook&lt;/em&gt; meant an ordered chain of actions. &lt;em&gt;Adapter&lt;/em&gt; meant the per-agent binding at adapters//…. The table I sat down to invent had been the truth of the code since half-way to 1.0. I was writing it down, not designing it.&lt;/p&gt;

&lt;p&gt;Writing it down still mattered. The contract was implicit until that table existed. New ports would have slipped in by accident the same way old ones had. But the &lt;em&gt;design&lt;/em&gt; work had already happened, one commit at a time, while I thought I was just shipping features.&lt;/p&gt;

&lt;p&gt;That is the angle worth chewing on. The names I had given things while building features carried more architectural weight than any single commit they appeared in. &lt;em&gt;Guide&lt;/em&gt; and &lt;em&gt;corpus&lt;/em&gt; drew the line between rules-loaded-every-turn and reasoning-loaded-on-demand. &lt;em&gt;Sensor&lt;/em&gt; drew the line between an automated check and an aspirational rule. &lt;em&gt;Action&lt;/em&gt; and &lt;em&gt;playbook&lt;/em&gt; drew the line between an atomic unit of work and an ordered chain of them. Every one of those lines was a load-bearing distinction in the runtime, and I had named them all before I knew they were load-bearing.&lt;/p&gt;

&lt;p&gt;The corollary is the part to hold onto: when the refactor came, &lt;strong&gt;I did not have to invent a single new concept&lt;/strong&gt;. I had to relocate the code that &lt;em&gt;implemented&lt;/em&gt; the concepts. The concepts themselves stayed put. Naming had front-loaded the design work. The refactor was a directory move on top of a vocabulary that was already correct.&lt;/p&gt;

&lt;p&gt;This is the underrated payoff of taking names seriously while building. If you have named the abstractions well in version 0.3, you can refactor the runtime in version 1.0 without renaming anything. If you have named them badly, version 1.0 starts with a six-month tax to fix the words before you can fix the code. The cheap version of a hard refactor is the one where the user-facing vocabulary is already correct and only the implementation moves.&lt;/p&gt;

&lt;p&gt;It also changes the social cost of the refactor. A user who learned &lt;em&gt;guide&lt;/em&gt; and &lt;em&gt;corpus&lt;/em&gt; in 0.3 still knows what those words mean in 1.0. The blog post they wrote about the harness in 0.5 is still accurate. The wiki page in their team’s Notion has not gone stale. Refactor-without-renames is the kind a user does not feel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing it down made it real
&lt;/h3&gt;

&lt;p&gt;Once I admitted what was happening, the next move was to make the framing physical. Not “we say it is a framework now.” That is marketing. The framework had to &lt;em&gt;look&lt;/em&gt; like a framework when you opened the source tree, and behave like one when you read the code.&lt;/p&gt;

&lt;p&gt;The plan went through six phases. Each had a small handful of commits, none too clever, each one a small structural improvement. Order mattered, because some moves enabled others.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzn1bd0kdy7phslii0puq.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzn1bd0kdy7phslii0puq.png" width="800" height="45"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The phased refactor, in order. Each arrow is a phase the next one depends on.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A few things about the order are worth pulling out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 0 was the cheapest and the most important.&lt;/strong&gt; Before any code moved, I wrote eight architecture decision records and one port contract per abstraction. Each ADR was a single page: context, decision, consequences, alternatives considered. Each port contract was a single page: path convention, required frontmatter, cascade behavior, an example, the command that scaffolds it. The total page count was around twenty. Writing them took two days. They turned out to be the most useful pages in the whole project, because every subsequent phase referenced them. When a phase started, I re-read the relevant ADR; when a phase finished, I re-read it again to check we had stayed inside the lines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 1 was the physical line down the middle.&lt;/strong&gt; Every Go file under the repo root got moved into internal/framework/. The CLI entrypoint moved to cmd/keystone/. The template tree relocated to internal/framework/scaffold/templates/. After Phase 1, you could point at the framework in the directory tree without using words. That is a small thing that turns out to be a big thing. Two months later, when a contributor asks “is this framework behavior or content?”, the answer is a path, not a paragraph.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phases 2 through 5 were small structural improvements stacking on top of that line.&lt;/strong&gt; JSON-only config (Phase 2), because YAML had drifted across half a dozen schemas during 0.x. Vendored read-only plugins (Phase 3), because the plugin model had been “edit the markdown in harness/policies/” and that turned out to be the wrong default. Projects would silently diverge from upstream and nobody would notice. Conventions, generators, and a doctor command (Phase 4), because once you have named ports you can write generators that scaffold an adapter for each port. Per-port token budgets (Phase 5), because once you have named ports you can also count tokens per port and tell the user when one is bloated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 6 was the documentation phase.&lt;/strong&gt; An upgrade guide for 0.x users. A compatibility doc spelling out what 1.x promised to keep stable. Nothing structural changed in Phase 6. The point was to make a contract that the next year’s worth of changes had to respect.&lt;/p&gt;

&lt;p&gt;Each phase landed in a separate set of commits. Each commit was small enough to read in one sitting. None of them were a “big refactor commit.” That is the texture of a refactor that respects the work already there: many small surgical moves, each one preserving behavior, with the architecture emerging from the sum.&lt;/p&gt;

&lt;h3&gt;
  
  
  The plugin draft we threw away
&lt;/h3&gt;

&lt;p&gt;I want to talk about a wrong turn, because every refactor has one, and the ones that do not get talked about tend to be the ones that bite later.&lt;/p&gt;

&lt;p&gt;The first draft of the 1.0 plan made the built-in defaults (universal guides, lifecycle playbook, default sensors, per-agent adapters) into &lt;em&gt;first-class plugins shipped embedded in the binary&lt;/em&gt;. The plan read:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The universal engineering corpus/guides, the lifecycle actions, the task playbook, and the default sensors all become first-class policy plugins — same shape as user-installed plugins, loaded by the same engine.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The promise was symmetry. Built-ins and external policy would travel the same pipeline. One loader, one cascade, no special cases. It was clean. It was elegant. It also had a giant cost buried in the elegance: the moment defaults are loaded as plugins, &lt;em&gt;editing a default&lt;/em&gt; stops being “edit a markdown file in your repo” and becomes “fork the embedded plugin or override it from a higher layer.”&lt;/p&gt;

&lt;p&gt;That breaks the Rails-style ergonomics I wanted at the project layer. The whole point of conventions over configuration is that the conventional file is just sitting there in the repo, your repo, your git, ready to be edited like any other file. If the default lives inside the binary and only appears in the consumer’s repo as a &lt;em&gt;shadow&lt;/em&gt; that overrides it, you have turned a one-line edit into a four-step debugging session. &lt;em&gt;Where is this rule actually coming from? Is the override winning? Why is my edit not taking effect?&lt;/em&gt; I have seen that pattern in tools that load defaults from inside their distribution, and the answer is always the same: it is a constant low-level tax on every user, paid forever.&lt;/p&gt;

&lt;p&gt;I caught it because I sat with the plan for a day before starting any code. Two of the ADRs (number five, &lt;em&gt;Conventions, not plugins&lt;/em&gt;, and number two, &lt;em&gt;Framework / client boundary&lt;/em&gt;) ended up being the place where the wrong turn got walked back. The decision was: defaults are &lt;em&gt;scaffolded&lt;/em&gt; into the consumer’s harness// on keystone init, from embedded templates. From that moment on, defaults are project content. The user edits them as markdown files in their own git. There is no override mechanism for defaults, because there is nothing to override. There is just one file sitting on their disk.&lt;/p&gt;

&lt;p&gt;Plugins still exist. They do one job: share policy across projects. Read-only, vendored, hash-verified, drift-reset on the next run. They are not the mechanism for shipping defaults, and the mechanism for shipping defaults is not the mechanism for sharing policy. Two concerns, two mechanisms. The earlier symmetry was buying elegance with the user’s debugging time.&lt;/p&gt;

&lt;p&gt;The lesson I want to draw from this is narrower than “avoid premature symmetry.” It is: when two mechanisms produce a surface that looks identical to the user, you should still ask whether the &lt;em&gt;editing model&lt;/em&gt; is identical. If the user edits both the same way, symmetry is paying its rent. If the user edits one of them and merely &lt;em&gt;reads&lt;/em&gt; the other, you have two concerns that happen to share a shape, and unifying them costs you the editing UX of the one the user actually edits.&lt;/p&gt;

&lt;p&gt;I had to throw away two days of plan-writing to walk that back. It was worth it. &lt;em&gt;The walkback is what made the 1.0 surface usable.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Code moved, concepts did not
&lt;/h3&gt;

&lt;p&gt;When 1.0 shipped, I went back through the changelog to count. The framework had moved every Go file into a new location. It had dropped YAML. It had rewritten the cascade resolver. It had added a vendored plugin model, a doctor command, a budget command, a port-level scaffold generator, and per-agent adapter regeneration. The repo layout looked nothing like 0.x.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The user-facing vocabulary had not changed.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A 0.x user who never read the 1.0 plan could open the new docs and find every word they already knew. &lt;em&gt;Guide&lt;/em&gt; still meant rules loaded every turn. &lt;em&gt;Corpus&lt;/em&gt; still meant reasoning loaded on demand. &lt;em&gt;Sensor&lt;/em&gt; still meant an automated check. &lt;em&gt;Action&lt;/em&gt; and &lt;em&gt;playbook&lt;/em&gt; still meant what they meant. The only new word at 1.0 was &lt;em&gt;port&lt;/em&gt;, and &lt;em&gt;port&lt;/em&gt; was a name for something the user already understood without the name. Namely, the named slot a piece of content lives in.&lt;/p&gt;

&lt;p&gt;This is the payoff I underestimated when I was naming things in 0.2 and 0.3. The cost of naming a thing well at the start is that you have to actually sit with it for a few minutes and ask whether the name says what the thing does. The benefit is that later, when you refactor the runtime, you do not have to renegotiate the contract with every user who learned the old word. &lt;em&gt;The user’s&lt;/em&gt; mental model does not change. Only the implementation does.&lt;/p&gt;

&lt;p&gt;A refactor that does not rename anything is the cheapest kind for a user. They keep their docs, their wiki, their training onboarding. They keep the words they say out loud when describing the product to a teammate. The internals can move freely as long as the names stay fixed.&lt;/p&gt;

&lt;h3&gt;
  
  
  How shapes form under names
&lt;/h3&gt;

&lt;p&gt;Step back from Keystone for a second. The pattern is broader than one project.&lt;/p&gt;

&lt;p&gt;Tools grow into frameworks. They almost always do, if they live long enough. The path looks like this: a small utility solves a small problem. It picks up a configuration file. The config file grows extension points. The extension points need naming conventions. The naming conventions need a resolver. The resolver needs ordering rules. The ordering rules need a way to opt out, then a way to opt back in. Suddenly there is a runtime, and the runtime has a contract with everything plugged into it, &lt;em&gt;and the contract is a framework whether you call it one or not&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The interesting question is when to notice. Too early and you are over-designing: naming abstractions before you have three concrete uses for them, building cascade rules for a single tier, writing port contracts for one port. Too late and the runtime has accumulated implicit contracts that nobody wrote down, and the cost of writing them down is paid in surprise regressions when you try to clean up.&lt;/p&gt;

&lt;p&gt;The way I think about it now: the moment you find yourself adding a &lt;em&gt;resolver&lt;/em&gt; (anything that picks a winner among multiple sources of the same concept) you are in framework territory. Lockfiles and migrations are also strong signals. Per-target rendering (the adapter layer) is a hard signal. Any one of those, taken alone, is not enough. Two of them together is. Three of them together and the question is not &lt;em&gt;if&lt;/em&gt; you are building a framework, it is whether the framework is going to be &lt;em&gt;honest&lt;/em&gt; about itself in its README.&lt;/p&gt;

&lt;p&gt;If you only catch it at three, the way I did, you are not in trouble. You are in the spot where naming the shape costs you a few days of writing and a few weeks of refactoring, and the user-facing surface comes out untouched on the other side. If you catch it at six or seven signals deep, the cost is higher, because by then there are real users with real assumptions and the cleanup runs into compatibility costs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three things to do this week
&lt;/h3&gt;

&lt;p&gt;If you are reading this and recognizing your own tool in the description, here is what I would do this week, in order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write the port table.&lt;/strong&gt; Open a new document. List every named extension point in the tool. For each one, write the path convention, the activation rule, and the shape of the file (or config) that goes there. Do not stop until the table is exhaustive. The table is the easiest possible draft of the framework contract. If it takes an hour, you were already a framework and your vocabulary was holding the design up; if it takes a week and you keep discovering anonymous concepts, you have found the work that needs naming first. Either way, this is the highest-value hour you can spend on the project this week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Draw the physical boundary.&lt;/strong&gt; Look at your source tree. Can you point at “framework code” and “content the framework happens to ship” using a path, or do you have to use words? If it is words, move directories until it is a path. This is the single most clarifying refactor for a maturing tool, and the tests will catch you if you break anything along the way. One day’s work for a small project, a week for a medium one. From the moment it lands, every conversation about “where does this go?” gets shorter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write ADRs for the decisions you have already made.&lt;/strong&gt; Not the future decisions, but the ones already locked in by the code. Three to ten ADRs of one page each. Context, decision, consequences, alternatives considered. Future you will thank present you the next time someone asks why X is the way it is. The act of writing them often surfaces a wrong turn you can still back out of cheaply, exactly like the plugin draft I walked back. Better to find that during ADR-writing than during a refactor PR.&lt;/p&gt;

&lt;p&gt;After those three, you will have a clear picture of what you have, where it lives, and why. The work that comes next is the work that actually changes things — the renaming, if any, the directory moves, the new scaffolders, the migration path for existing users. That work is real and it takes time. But you can scope it, plan it, and ship it in phases that do not break users, because the contract you wrote down in the first three steps is the contract every subsequent phase has to honor.&lt;/p&gt;

&lt;p&gt;The thing I keep coming back to from the Keystone refactor is how much of the work had been done already by past versions of me when I named &lt;em&gt;guide&lt;/em&gt; and &lt;em&gt;corpus&lt;/em&gt; and &lt;em&gt;sensor&lt;/em&gt; and &lt;em&gt;action&lt;/em&gt; in different commits weeks apart. They were not designing a framework. They were each adding one feature and giving it a name. The framework emerged from the sum of those names. The 1.0 refactor was the moment I looked up and noticed.&lt;/p&gt;

&lt;p&gt;If you have been adding features and naming them well, your framework is already there. It is just waiting for you to write it down.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keystone
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.tacoda.dev/keystone/" rel="noopener noreferrer"&gt;Keytsone&lt;/a&gt; is now 2.1.1! It is open source and MIT-licensed. I am very open to feedback, so if you have any, please create a discussion or an issue on &lt;a href="https://github.com/tacoda/keystone" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. It has a &lt;em&gt;real&lt;/em&gt; chance of landing.&lt;/p&gt;

&lt;p&gt;A lot landed between the 1.0 cut and 2.1.1, and all of it sits on top of the framework/client split 1.0 introduced. The shape of the project didn’t change. The surfaces around it did.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.0&lt;/strong&gt; was the big one, shipped June 17. A new primitive taxonomy ran through the harness end to end. An in-binary &lt;strong&gt;MCP server&lt;/strong&gt; moved keystone onto the same wire its agents already speak, so there’s no separate process to babysit. A &lt;strong&gt;localhost dashboard&lt;/strong&gt; surfaced live state for the first time. An &lt;strong&gt;eval suite&lt;/strong&gt; came with it. The disk layout moved alongside all of that, and the upgrade was still one command: keystone migrate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.1.0&lt;/strong&gt; followed the next day. The old patches mechanism retired in favor of a versioned migrations subsystem with paired Up/Down transforms under migrations//, run with keystone migrate up | down | status. The plugin→policy rename finished across Go source, JSON schemas, and docs in the same release. Installs that hadn’t migrated yet started warning and continuing instead of breaking, so the upgrade path stays soft.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.1.1&lt;/strong&gt; reshaped the dashboard. It’s now an HTMX SPA with a single &lt;/p&gt; swap target, real back/forward navigation, and fragment responses keyed off HX-Request. The 14 single-purpose nav links collapsed into five sections: &lt;strong&gt;Observability, Harness, Sources, Flywheels, Quality.&lt;/strong&gt; SSE topic narrowing means a widget only refreshes when its own path changes. A per-session audit log lands at .keystone/state/audit/session--.jsonl, opened with O_CREATE|O_EXCL so nothing ever overwrites a prior run. &lt;em&gt;Cmd+K&lt;/em&gt; (&lt;em&gt;Ctrl+K&lt;/em&gt; on Windows and Linux) opens a search popover from any page.

&lt;p&gt;If you’re still on 1.x, the upgrade is the same as the install (except Brew, which has an upgrade subcommand. It carries you the whole way to 2.1.1. To bring a project harness up-to-date with the new core framework structure, run keystone migrate up. This will patch core files only and will never touch user-edited files.&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>harnessengineering</category>
      <category>agenticai</category>
      <category>agentharness</category>
    </item>
    <item>
      <title>The Harness Is Also Onboarding</title>
      <dc:creator>Ian Johnson</dc:creator>
      <pubDate>Thu, 18 Jun 2026 15:14:48 +0000</pubDate>
      <link>https://dev.to/tacoda/the-harness-is-also-onboarding-1lba</link>
      <guid>https://dev.to/tacoda/the-harness-is-also-onboarding-1lba</guid>
      <description>&lt;p&gt;A new engineer joins the team. Their first question (even if they don’t know it): &lt;em&gt;Where should I read first to understand how things work here?&lt;/em&gt; I started to answer with the usual list — the README, the architecture doc, the deployment runbook — and stopped halfway through. The most useful document in the repo, by a wide margin, was the project’s CLAUDE.md. I had not written it for an agent, not a human. It still answered most questions better than the docs I had written for humans.&lt;/p&gt;

&lt;p&gt;After reading, a new team member will have sharper questions than the previous three hires had asked in their first week. The questions are sharper because the harness had taught him the team’s conventions, naming, escalation patterns, and what not to do. The things that take months to absorb from code review and tribal memory had been compressed into a file he read on day two.&lt;/p&gt;

&lt;p&gt;The harness is also onboarding. We had not designed it that way, but it was working that way anyway, and once I noticed it I started writing it that way on purpose.&lt;/p&gt;

&lt;p&gt;This is the post about what changed when I noticed. Not a lot, in terms of effort. A lot, in terms of who the file was for and how the team treated it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The README is aspirational; the harness is forensic
&lt;/h3&gt;

&lt;p&gt;A harness captures the things the team has learned, mostly by paying for them. Every rule has an incident behind it. Every annotation explains a choice that was not obvious at the time. Every scope adjustment reflects a misread that the team made, noticed, and corrected. The file accumulates the way a scar accumulates; one event at a time, each event leaving a mark.&lt;/p&gt;

&lt;p&gt;A traditional onboarding doc is written before the team has made the mistakes. It says what the architecture is supposed to look like, what the deploy process is supposed to be, what the conventions are supposed to be. It is aspirational. The harness is forensic.&lt;/p&gt;

&lt;p&gt;The forensic document is more useful for a new hire because it tells them what actually happens when you do the wrong thing, not what you are supposed to do in theory. The new hire’s first PR is going to break three conventions. The harness tells them which three, and why, before they write the PR. The architecture doc does not. &lt;em&gt;It cannot&lt;/em&gt;, because the conventions did not exist when it was written.&lt;/p&gt;

&lt;p&gt;The accidental onboarding works because the harness is honest about the team’s reality in a way the docs built for humans tend not to be. A new hire does not need to know what the system &lt;em&gt;should&lt;/em&gt; be. They need to know what happens when they touch it.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the README leaves out
&lt;/h3&gt;

&lt;p&gt;The README has the project’s pitch and the commands to run. The architecture doc has the diagrams. The contributing guide has the PR template. None of these tell a new engineer what the team has learned to be careful about.&lt;/p&gt;

&lt;p&gt;The harness does. Here are four kinds of lines the harness tends to carry and the human docs tend not to:&lt;/p&gt;

&lt;p&gt;A rule that says &lt;em&gt;in the migrations directory, always test against a real database&lt;/em&gt;. That line tells the new hire this team has paid for a mocked-migration mistake. They learn the lesson without paying for it.&lt;/p&gt;

&lt;p&gt;A rule that says &lt;em&gt;errors in this service propagate as Result types, not exceptions&lt;/em&gt;. That line tells the new hire about a convention that pervades the codebase and would otherwise be invisible until their first review came back red.&lt;/p&gt;

&lt;p&gt;A rule that says &lt;em&gt;do not edit anything in&lt;/em&gt; &lt;em&gt;legacy/; coordinate in&lt;/em&gt; &lt;em&gt;#team-platfrom first&lt;/em&gt;. That line tells the new hire about a political-technical boundary that nobody put in a doc but everyone on the team knows.&lt;/p&gt;

&lt;p&gt;A rule that says &lt;em&gt;the test for this module hits a real Redis; bring it up with&lt;/em&gt; &lt;em&gt;docker-compose up redis&lt;/em&gt;. That line gives them the exact command they would otherwise spend twenty minutes searching for.&lt;/p&gt;

&lt;p&gt;The harness is dense. Every line is something the team chose to say. A new hire reads density they can use; they do not read fluff. That density is also why the harness wins against the README on day two. The README has the bird’s-eye view, but the new hire does not need a bird’s-eye view to ship their first PR. They need the things that will get them in trouble.&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing for two readers at once
&lt;/h3&gt;

&lt;p&gt;Once you accept the dual role, a few writing choices change. None of them make the harness longer. They make it more useful per line.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Annotate the reason.&lt;/strong&gt; A rule that says &lt;em&gt;use the structured logger&lt;/em&gt; is enough for the agent. A rule that says &lt;em&gt;use the structured logger; we had a parsing outage in February because grep-style logs broke the alert pipeline&lt;/em&gt; tells the new hire the why. The agent does not need the why to apply the rule. The new hire does. The cost is one extra line. The benefit is a harness that explains itself the first time it is read.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Name the conventions, not just the rules.&lt;/strong&gt; The harness gets sharper for the agent if rules are imperative — &lt;em&gt;always X&lt;/em&gt;, &lt;em&gt;never Y&lt;/em&gt;. The harness gets sharper for the new hire if the conventions have names. &lt;em&gt;We call this the import-flow contract&lt;/em&gt; is more useful for a human than a list of three imperatives in the import directory, even if the latter is more usable for the agent. The trick is to do both: name the convention in the section header, then list the imperatives below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Make the boundaries explicit.&lt;/strong&gt; The harness should say which modules are owned by which sub-team, which directories require coordination, which areas are stable and which are in flux. The agent uses this to know where to be careful. The new hire uses it to know who to ask. The same line does both jobs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep it readable end to end.&lt;/strong&gt; A harness that is sectioned and skimmable is a harness a new hire can actually finish in an hour. A harness that is one unbroken list of rules is a harness the new hire gives up on at minute eleven. The agent does not care about pacing. The human reader does, and the human reader is the one who closes the file if it loses them.&lt;/p&gt;

&lt;p&gt;The dual-role harness is not longer than the agent-only harness. It is the same length, with two or three extra lines per major rule and a structure that supports both readers.&lt;/p&gt;

&lt;h3&gt;
  
  
  The onboarding flow that puts the harness first
&lt;/h3&gt;

&lt;p&gt;Our current first-week flow for a new engineer:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day one: environment.&lt;/strong&gt; Clone the repo, install dependencies, run the test suite locally. This is the boring stuff and it has not changed. The harness does not help with this; a good make bootstrap does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day two: read the project&lt;/strong&gt;  &lt;strong&gt;CLAUDE.md end to end.&lt;/strong&gt; Read it slowly. Ask questions in the team channel as they come up. The questions become annotations the harness was missing. The whole day is for this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day three: read one subdirectory’s&lt;/strong&gt;  &lt;strong&gt;CLAUDE.md.&lt;/strong&gt; The one closest to the engineer’s first task. The path-scoped harness has the local conventions, which are denser and more specific than the project-level rules. By the end of the day the engineer has a working model of the area they are about to change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day four: a small PR, with the agent assisting.&lt;/strong&gt; The harness catches the failure modes the engineer would otherwise walk into. The human review is on the substance of the work, not on the conventions, because the harness handled the conventions before the PR was opened.&lt;/p&gt;

&lt;p&gt;The first week ends with the engineer having absorbed more of the team’s accumulated practice than they would have in two weeks under the previous flow. The cost of writing the harness for the dual role was small — three or four hours of editing, spread over a couple of evenings. The return compounds on every subsequent new hire.&lt;/p&gt;

&lt;h3&gt;
  
  
  A new hire’s questions are a free audit
&lt;/h3&gt;

&lt;p&gt;The thing I underestimated, before noticing this pattern, was how much signal a new hire’s questions are for the harness itself.&lt;/p&gt;

&lt;p&gt;A new hire asking &lt;em&gt;why does this module have a different error pattern than the others&lt;/em&gt; is telling me the harness does not name the convention clearly enough, or does not name it at all. A new hire confused about which directory a particular kind of code belongs in is telling me the harness does not encode the structural rule. A new hire surprised by a CI failure is telling me the pre-commit hooks are not communicating their constraints clearly. None of these are gaps in the new hire’s understanding. They are gaps in my document.&lt;/p&gt;

&lt;p&gt;I treat the new hire’s first month as a free audit of the harness. Their questions are the gaps. Their confusions are the rules that need to be sharper. I patch as the questions come in, which means by the time the next new hire arrives, the document is a measurable amount better. The cycle has run six times now and the file has gotten better every single round.&lt;/p&gt;

&lt;p&gt;This is the second dividend of the dual role. The harness is onboarding. The onboarding feedback is harness improvement. The flywheel runs in both directions, and it runs for free. The new hire is going to ask the questions either way.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this does to the architecture doc
&lt;/h3&gt;

&lt;p&gt;The first time I noticed all this, I asked whether the architecture doc was still earning its place. The answer turned out to be yes, but a narrower yes than before.&lt;/p&gt;

&lt;p&gt;The architecture doc has the diagrams, the high-level shape of the system, the names of the services and how they communicate. It is the map. The harness is the ground truth: the conventions, the constraints, the practices the map does not capture.&lt;/p&gt;

&lt;p&gt;The new hire reads the map first to know what they are looking at, then the ground truth to know how to walk on it. Both are still required. The harness did not replace the architecture doc; the harness made the architecture doc a smaller document, because the harness was carrying the load the architecture doc had been trying to carry and failing.&lt;/p&gt;

&lt;p&gt;The map is short. The ground truth is detailed. That division turns out to be the right one. The diagram I used to update every quarter to keep up with the conventions is now stable for a year at a time, because the conventions live in the file that gets edited every week anyway.&lt;/p&gt;

&lt;h3&gt;
  
  
  Read your harness like a new hire this week
&lt;/h3&gt;

&lt;p&gt;If you are running an AGENTS.md or CLAUDE.md or any equivalent file on a team project, three things to do this week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open the harness and read it cold.&lt;/strong&gt; Pretend you joined the team last Monday. You do not know what the codebase does. You do not know who owns what. Read every line. Note every sentence that assumes context a stranger would not have. That list is your patch list. Annotate the reasons. Name the conventions. Fix the unexplained jargon. Do this in one sitting if you can; the cold-reader perspective decays fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hand the patched harness to the next hire instead of writing them a new onboarding doc.&lt;/strong&gt; Sit with them while they read it. Write down every question they ask out loud. Their questions are your next patch list. Do not defend the existing document. Edit it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Move one section out of the architecture doc and into the harness.&lt;/strong&gt; Pick the section that has gone stale fastest — usually the conventions section, or the section listing which team owns which module. Put it where the file is actually updated. Delete the now-empty section in the architecture doc, or leave a one-line pointer. The architecture doc gets smaller. The harness gets one more thing it is honest about.&lt;/p&gt;

&lt;p&gt;After those three, the harness is doing on purpose what it had already started doing by accident. A document that is read by both the agent and the new hire has to earn its place twice. The rules that earn their place twice are the rules worth keeping. The rules that earn their place only for the agent are still load-bearing, but the dual-role audit makes them visible, which is the first step to making them better.&lt;/p&gt;

&lt;p&gt;The harness is doing more work than you think. Notice the second job. Then write it on purpose.&lt;/p&gt;

</description>
      <category>knowledgesharing</category>
      <category>agenticai</category>
      <category>harnessengineering</category>
      <category>employeeonboarding</category>
    </item>
    <item>
      <title>Keystone 2.0 — A Worthy 2.0</title>
      <dc:creator>Ian Johnson</dc:creator>
      <pubDate>Wed, 17 Jun 2026 19:41:27 +0000</pubDate>
      <link>https://dev.to/tacoda/keystone-20-a-worthy-20-2jhp</link>
      <guid>https://dev.to/tacoda/keystone-20-a-worthy-20-2jhp</guid>
      <description>&lt;p&gt;&lt;a href="https://www.tacoda.dev/keystone/" rel="noopener noreferrer"&gt;https://www.tacoda.dev/keystone/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A major version should mean something. If you ship 2.0 and a user opens the repo to find the same shape with a new number on it, you have wasted their attention. The shelf is already full of frameworks that did that. So when I started planning the next Keystone release, the question I kept asking was simple: what would make a developer say, out loud, “this is a different tool now”?&lt;/p&gt;

&lt;p&gt;Two answers held up. The first was &lt;em&gt;shape&lt;/em&gt;. Keystone 1.x had the right ideas (guides, corpus, sensors, actions, playbooks, adapters) but the taxonomy stopped just short of being a real framework vocabulary. A user could not look at the abstractions and immediately know where the next thing they needed lived. The second was &lt;em&gt;observability&lt;/em&gt;. The harness was healthy, but you had to take my word for it. There was no operator view. No dashboard. No way to see, at a glance, what the agent saw.&lt;/p&gt;

&lt;p&gt;2.0 fixes both.&lt;/p&gt;

&lt;h3&gt;
  
  
  A real framework vocabulary
&lt;/h3&gt;

&lt;p&gt;Keystone is the agent harness framework. That has been the pitch since 1.0. The Rails analogy is the right one. A working set of components, conventions, and slots, so the team building on top isn’t inventing the world from scratch each Monday morning.&lt;/p&gt;

&lt;p&gt;2.0 makes the vocabulary explicit. &lt;strong&gt;Eleven primitive kinds in two layers&lt;/strong&gt; : Framework — guide, corpus, sensor, action, playbook, eval, source — and Agent — rule, skill, subagent, command, persona. Every file in .keyston/harness/ carries canonical frontmatter declaring its kind, id, and per-kind required fields. The walker emits a single .keystone/INDEX.json that every tool reads first. You stop searching the directory tree for where a thing lives. You ask the index.&lt;/p&gt;

&lt;p&gt;This is the part that makes 2.0 feel like a different tool. The old harness/ layout worked. The new .keystone/harness/ layout &lt;em&gt;teaches&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  An operator view
&lt;/h3&gt;

&lt;p&gt;keystone web serve opens localhost:4773. The dashboard is fourteen pages of insight into the harness you just installed: home, metrics, insights, primitives, policies, investigator, sources, verify, prune, inbox, flywheels, evals, search, graph. Same binary. HTMX plus SSE; fsnotify on .keystone/ swaps fragments when files change. Open it in a browser, edit a guide in your editor, and watch the dashboard update without a refresh.&lt;/p&gt;

&lt;p&gt;A harness you can see is a harness you will actually maintain. Before 2.0, “is the harness healthy?” was a question you answered by reading files. Now it’s a tab you keep open.&lt;/p&gt;

&lt;h3&gt;
  
  
  The other things worth knowing
&lt;/h3&gt;

&lt;p&gt;A few more pieces ship in 2.0 that earn their own mention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A built-in MCP server.&lt;/strong&gt; keystone mcp install --agent cladue-code writes .mcp.json in one shot. Twenty-one tools, four prompts, resources for index, primitives, sources, and skills. The same binary that authors the harness now dispatches it to the agent over the model-context-protocol. One source of truth, one runtime contract.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evals with baseline diffs.&lt;/strong&gt; A new framework primitive lives at .keystone/harness/evals//EVAL.md. Static and sensor levels in 2.0; agent level reserved for 2.1. The interesting verb is keystone eval run --baseline  — it materializes the ref in a git worktree, runs both sides, and diffs the results into a regression report. Your harness gets its own test suite, and the suite knows what “last week” looked like.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A slash-command surface in 2.0.1.&lt;/strong&gt; Every Keystone action ships as a /keystone:* skill, projected into .claude/skills/ on init. /keystone:bootstrap, /keystone:learn, /keystone:synthesize, /keystone:audit, /keystone:spec, /keystone:orient, /keystone:review. The agent already knows how to call them. You just say the word.&lt;/p&gt;

&lt;p&gt;There’s more: keystone search over every primitive, keystone graph --format mermaid|dot for a relationship view, keystone watch for an fsnotify loop that re-indexes on save, the plugin → policy rename, and the retirement of --harness-root in favor of a fixed framework path. The website has the full tour.&lt;/p&gt;

&lt;h3&gt;
  
  
  Moving from 1.x
&lt;/h3&gt;

&lt;p&gt;One command. keystone migrate moves harness/ to .keystone/harness/, renames plugins/ to policies/, rewrites keystone.json to the v2 schema, regenerates the index, and refreshes host projections. It is idempotent. Pair it with keystone snapshot save --label pre-2.0 for insurance and the rollback is a single restore away.&lt;/p&gt;

&lt;h3&gt;
  
  
  Try it
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;tacoda/tap/keystone
keystone init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full walkthrough (every primitive kind, every CLI verb, the MCP tool surface, the dashboard tour) lives at &lt;a href="https://www.tacoda.dev/keystone/" rel="noopener noreferrer"&gt;tacoda.dev/keystone&lt;/a&gt;. Keystone is MIT-licensed and agent-agnostic; Claude Code, Cursor, Codex, Aider, Continue, Cline, Goose — whichever one you’ve already settled on, the same harness drives it.&lt;/p&gt;

&lt;p&gt;2.0 is the version where the framework shape and the operator view both showed up. 1.0 was the right idea; 2.0 is the one that earns the number.&lt;/p&gt;

</description>
      <category>harnessengineering</category>
      <category>softwareengineering</category>
      <category>agenticaiarchitectur</category>
      <category>aitools</category>
    </item>
  </channel>
</rss>
