<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Harness Engineering - Medium]]></title>
        <description><![CDATA[Harness engineers are solving for the biggest challenges in the Software delivery cycle. Follow along for shared learning of what worked and didn’t work - Medium]]></description>
        <link>https://medium.com/harness-engineering?source=rss----c7a8b2228e23---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Harness Engineering - Medium</title>
            <link>https://medium.com/harness-engineering?source=rss----c7a8b2228e23---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 02:22:36 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/harness-engineering" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[AI Productivity Is Up. Are Your Metrics Keeping Up?]]></title>
            <link>https://medium.com/harness-engineering/ai-productivity-is-up-are-your-metrics-keeping-up-940bc88d6221?source=rss----c7a8b2228e23---4</link>
            <guid isPermaLink="false">https://medium.com/p/940bc88d6221</guid>
            <dc:creator><![CDATA[Harness Engineering]]></dc:creator>
            <pubDate>Fri, 22 May 2026 16:41:16 GMT</pubDate>
            <atom:updated>2026-05-22T16:41:17.836Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*PdHuYcwtQhLsQ8Az.png" /></figure><p>Authors: <a href="https://www.harness.io/authors/trevor-stuart">Trevor Stuart</a></p><p>For the past year, I’ve been hearing a version of the same thing from engineering leaders: AI tools are working, productivity is up, the business case is there. And yet, something about the picture still feels incomplete. So we decided to go find out how widespread that feeling actually is. We surveyed 700 engineers and managers across five countries, and published the results in the State of Engineering Excellence 2026.</p><p>89% of engineering leaders say developer productivity has improved since deploying AI. It’s a clean story. AI is working. Engineering teams are moving faster.</p><p>But, we also found that 81% of those same leaders say code review time has gone up since deploying AI. Significantly up, in a lot of cases. And, developers estimate that roughly a third of their day is now consumed by AI-related work that remains largely invisible to traditional productivity metrics.</p><p>So which is it? Is AI making engineering teams more productive, or simply shifting effort into places they don’t yet measure? After sitting with this data for a few weeks, the answer is both. That’s the more honest read, even if it’s less satisfying.</p><h3>The gap between generating code and shipping value</h3><p>AI has been very good at increasing output. Simultaneously, it has not automatically delivered more shipped value.</p><p>I talked to a customer recently, a large enterprise engineering org, and they were genuinely proud of how much their output metrics had improved. Lines of code written, PR velocity per developer, tickets closed, features delivered. All of it up. Then we dug into what was actually making it to production, and the numbers looked much less clean. A meaningful share of AI-generated code was not getting to production.</p><p>Most organizations can tell you how much AI code was accepted. Very few can tell you how much of it actually landed in production, and that’s the number that matters. Hard dollars spent on agent compute that never shipped anything isn’t a productivity story. That’s a visibility gap, and it’s one most organizations aren’t measuring today.</p><h3>What “invisible work” actually looks like</h3><p>The 31% figure, the estimated share of developer time now consumed by AI-related work that appears in no metric, probably sounds abstract until you break down what it actually is.</p><p>It’s a developer sitting with a pull request for 45 minutes because the AI-generated code is technically correct but written in a style nobody on the team recognizes, and they need to fully understand it before they can approve it. It’s debugging a subtle edge case that the AI missed, which takes longer to track down than writing the function would have. It’s working with 10 agents in parallel on 10 different tasks. None of this makes it into velocity or cycle time, and even code review metrics only catch a fraction of it.</p><p>What this data shows is that organizations are running a business where the costs are partially off the books. You can show your CFO a 20% productivity improvement and that’s true. You just can’t show them what it cost to get there.</p><h3>High confidence in a broken system is its own problem</h3><p>The finding that surprised me most: 89% of engineering leaders say their current metrics accurately reflect AI’s impact. And 94% say key factors like tech debt, validation time, and developer burnout are missing from those same metrics.</p><p>When there’s no established standard for measuring something, people default to trusting the frameworks they already know. Not because they’ve validated them for the new environment, but because they’re familiar. High confidence in an incomplete system is a coping mechanism, not an accuracy signal.</p><p>The lesson: confidence in your measurement system should go up as you add instrumentation, not stay high when important dimensions of the work are still invisible. When 94% of leaders acknowledge gaps and only 6% think they’re equipped to close them, that’s not a minor calibration issue. That’s a signal worth taking seriously.</p><h3>The trust problem is structural, not individual</h3><p>54% of practitioners fear individual performance evaluations based on AI productivity data. Managers, by contrast, show far greater comfort with these systems: they are nearly four times more likely than developers to report having no concerns at all.</p><p>Measurement systems almost always get built top-down, by the people who won’t be measured by them. The practitioners who experience the day-to-day pressures of AI adoption, and who understand where invisible overhead actually lives, are rarely involved in defining the frameworks used to measure it. The result is a system that captures what leadership can see and misses what developers actually experience.</p><p>What developers said they need is straightforward: keep improvement data separate from performance evaluation, be transparent about what’s being measured, and involve them in defining the metrics. None of that is technically hard. It requires organizational commitment. When measurement feels like surveillance, you don’t get accurate data. You get people performing for the system instead of working in it.</p><h3>What we’re doing about it at Harness</h3><p>The productivity gains from AI are real. The problem is that organizations are making multi-year investment decisions with dashboards built for a different era, and the gap between what those dashboards show and what’s actually happening widens as AI adoption scales.</p><p>This is a problem we’ve been thinking deeply about at Harness. We’re working on new capabilities in Software Engineering Insights ( <a href="https://www.harness.io/products/software-engineering-insights">SEI</a>) that are designed to give engineering leaders visibility into the full picture: not just how much code is being generated, but how much of it is shipping, what the review and validation overhead actually looks like, and where AI spend is producing returns versus producing churn.</p><p>We believe the next generation of engineering measurement needs to be built for AI-native workflows, and we’ll be sharing more about that direction in the coming weeks.</p><p>Getting the measurement right isn’t a reporting exercise. It’s what makes the productivity gains from AI sustainable.</p><p>Download the full State of Engineering Excellence 2026 report [here].</p><p><em>Originally published at </em><a href="https://www.harness.io/blog/we-re-measuring-the-gains-and-missing-the-costs"><em>https://www.harness.io</em></a><em> on May 13, 2026.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=940bc88d6221" width="1" height="1" alt=""><hr><p><a href="https://medium.com/harness-engineering/ai-productivity-is-up-are-your-metrics-keeping-up-940bc88d6221">AI Productivity Is Up. Are Your Metrics Keeping Up?</a> was originally published in <a href="https://medium.com/harness-engineering">Harness Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What a Context Graph Actually Is, and How to Build One]]></title>
            <link>https://medium.com/harness-engineering/what-a-context-graph-actually-is-and-how-to-build-one-d3670f835505?source=rss----c7a8b2228e23---4</link>
            <guid isPermaLink="false">https://medium.com/p/d3670f835505</guid>
            <dc:creator><![CDATA[Harness Engineering]]></dc:creator>
            <pubDate>Fri, 22 May 2026 16:39:43 GMT</pubDate>
            <atom:updated>2026-05-22T16:39:44.354Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*BQg58j82Ex5pFG7h.png" /></figure><p>Authors: <a href="https://www.harness.io/authors/sunil-gattupalle">Sunil Gattupalle</a>,<a href="https://www.harness.io/authors/dewan-ahmed"> Dewan Ahmed</a></p><p>Engineers have been shipping pieces of “the graph” for years. Service maps. Dependency graphs. Knowledge graphs. RDF triples. The newest entrant is the <em>context graph</em>, and the reason it shows up now is specific: software is increasingly executed by agents, and agents need a model of how work actually happens, not just an index of what exists.</p><p>This post is a practical, vendor-neutral walkthrough of context graphs: what they are, what separates them from a knowledge graph, the components you’ll end up building, and the pitfalls that bite teams who try to ship one. I’ll be drawing on engineering writing from Glean and Harness where they’ve published useful frames, but the design decisions apply regardless of stack.</p><h3>The shift from “what” to “how”</h3><p>A knowledge graph answers questions about state. What services exist. Which team owns which repo. Which ticket is linked to which incident. The graph is a snapshot of relationships at a point in time.</p><p>A context graph answers a different question. How does work flow through this organization? When a P1 fires, what sequence of actions usually resolves it? When a deal moves from “pilot created” to “closed-won,” what steps are between those states, who runs them, and how long do they take? When a service hits an error rate threshold, what’s the typical path from alert to mitigation?</p><p>The Glean engineering team puts it concisely: “what” exists vs. “how” change happens. Their model treats actions as first-class nodes in the graph, with edges encoding causality and correlation. Other formulations exist, but the central idea is consistent across them.</p><h3>Three layers that usually compose a context graph</h3><p>Most production designs end up with a layered architecture, even if teams don’t always name the layers the same way:</p><p><strong>Layer 1: knowledge graph.</strong> Entities and the static relationships between them. Service A depends on service B. User U owns repo R. Ticket T is linked to incident I. This is the substrate. Without it, you don’t know that “ACME Inc” in your CRM and “Acme” in support is the same customer, and any aggregate analysis turns to mush.</p><p><strong>Layer 2: personal graph (or activity stream).</strong> A per-user temporal sequence of actions: viewed doc, edited file, commented on PR, joined channel, deployed service. The signals are noisy on their own. Real work is messy. People context-switch constantly, reuse the same document across efforts, and abandon threads only to pick them back up days later. The job at this layer is to stitch raw events into coherent units of work.</p><p><strong>Layer 3: context graph.</strong> Aggregate, anonymized patterns derived from many personal graphs. This is where you get statements like “P1 incidents in this product area resolve in 30 minutes 80% of the time and almost always pass through these four steps.” It is a probabilistic model of organizational process, not a static workflow definition.</p><p>Another way to look at the same architecture: a context graph is two intertwined graphs operating over the same entities. One is structural — nodes and edges representing the static relationships in your organization. The other is executional — transitions and actions that move those entities through their lifecycle over time. The context graph emerges from their combination. Neither graph alone is sufficient: the structural one tells you what <em>could</em> be touched in a given situation, the executional one tells you what <em>actually</em> gets touched, and only the intersection produces a model that’s useful to an agent. The three layers above are one way to slice this; the structural/executional split is another, and they map onto each other cleanly. Layer 1 is mostly structural, Layer 2 is mostly executional, and Layer 3 is where the two are joined under a shared semantic frame.</p><p>Sitting under all three is a <strong>semantic layer</strong> that defines what each thing actually means: an “incident” in the graph maps to a specific schema, with specific attributes and lifecycle states, regardless of which tool emitted the event. Without this, you’re shoveling JSON between systems and hoping the LLM figures it out.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*yLpRtQM8F1ipi5s_.png" /></figure><h3>Why this is not the same as a knowledge graph</h3><p>This question comes up constantly, so it’s worth being precise. A knowledge graph is structural. It models entities and explicit relationships. A context graph adds time and behavior. It models the temporal sequences of actions that move entities through their lifecycle.</p><p>You can build a useful knowledge graph without any context graph. People have been doing it for decades with RDF, property graphs, ontologies, and graph databases. What you can’t build without a context graph is a model of how work actually happens in your organization.</p><p>Concretely, a knowledge graph might tell you:</p><ul><li>service payments-api depends on auth-service</li><li>auth-service is owned by team iam</li><li>the on-call for iam this week is Priya</li></ul><p>A context graph layered on top adds:</p><ul><li>65% of payments-api outages last quarter were caused by config changes in auth-service</li><li>the typical mitigation involves rolling back the change, paging the iam on-call, then reviewing the deploy in postmortem</li><li>time from alert to mitigation is bimodal: under 15 minutes when the alert correlates with a recent auth-service deploy, 45+ minutes otherwise</li></ul><p>Each of those statements requires walking entity relationships <em>and</em> looking at temporal action sequences. Neither layer is sufficient on its own.</p><h3>Why this is not process mining either</h3><p>There’s a related comparison worth making, because process mining shows up often in conversations about this and the differences are easy to miss. Traditional process mining assumes relatively structured enterprise workflows running through bounded event systems — ERP, CRM, BPM platforms with consistent event logs and a finite set of process types. The job is to reconstruct an actual process from those logs and then report or optimize against a predefined target. The environment is controlled. The schemas are known. The processes have names.</p><p>Context graphs operate in a different environment. The work being modeled is fragmented across chat, docs, tickets, source control, observability tools, calendars, and increasingly agent actions themselves. There is no single event log, no shared schema across tools, and no predefined target workflow to compare against. The underlying systems weren’t designed to emit process traces; the traces have to be inferred from messy signals across tools that don’t know about each other and weren’t built to be joined.</p><p>The objective also differs. Process mining tends toward reporting and optimization of workflows you already know exist. A context graph is trying to build an adaptive, agent-consumable model of how work actually happens across structured and unstructured systems — including the parts that no one has ever formally defined as a process. The output isn’t a dashboard for a process owner; it’s a substrate for agent reasoning that updates as the organization changes.</p><p>Put another way: process mining is an analytics problem in a controlled environment. Context graphs are a behavioral modeling problem in an uncontrolled one. The system isn’t mining workflows. It’s learning organizational behavior, and most of that behavior was never written down.</p><h3>Why agents need this</h3><p>LLMs can already call tools. The harder problem is that they don’t know which tools to call, in what order, on which entities, to accomplish a real task in your environment. They have no model of your organization’s process.</p><p>Documentation describes intent. Systems of record capture state. Neither captures the actual flow of work. When you ask an agent to “investigate this alert,” “draft this proposal,” or “onboard this customer,” it has to assemble the workflow itself, usually with limited success.</p><p>A context graph fills the gap. It gives an agent a learned model of “what tends to happen and in what order” for the situations the agent encounters. Instead of hard-coding workflows in playbooks, the system surfaces the most probable path for the current scenario, and the agent can deviate when the situation warrants.</p><p>There’s a related constraint that the Harness engineering team frames well: the context window is RAM, and RAM is finite. Every token spent on infrastructure noise is a token that can’t be spent on reasoning. A context graph is only useful if the agent can pull just the relevant slice of it into context at the moment it’s needed. Loading the whole graph blows the budget.</p><h3>What an event in the graph actually looks like</h3><p>There’s no standard schema yet, but the shape that recurs across implementations is roughly this. An abstracted trace step (the kind that gets aggregated into the context graph) might look like:</p><pre>{<br><br>  &quot;trace_id&quot;: &quot;trace_8f2a...&quot;,<br><br>  &quot;step_index&quot;: 3,<br><br>  &quot;timestamp_relative_ms&quot;: 142000,<br><br>  &quot;action_type&quot;: &quot;comment&quot;,<br><br>  &quot;tool_family&quot;: &quot;ticketing&quot;,<br><br>  &quot;entities&quot;: {<br><br>    &quot;incident_id&quot;: &quot;INC-2391&quot;,<br><br>    &quot;service_id&quot;: &quot;payments-api&quot;,<br><br>    &quot;team_id&quot;: &quot;iam&quot;<br><br>  },<br><br>  &quot;process_tags&quot;: [&quot;investigate_alert&quot;, &quot;p1_response&quot;],<br><br>  &quot;outcome&quot;: null,<br><br>  &quot;duration_ms&quot;: 86000<br><br>}</pre><p>Two things to notice. First, no raw text. No message bodies, no doc contents, no user identifiers. The aggregation is over abstracted steps, not raw activity. Second, knowledge graph entity IDs are first-class on every step. That’s how the context graph stays tied to the substrate. Without those IDs, the patterns you mine are interesting but not actionable.</p><h3>Building one: components that matter</h3><p>There’s no canonical architecture for context graphs yet. The components that recur across teams shipping one are roughly these.</p><h3>1. Deep connectors and event capture</h3><p>You can’t model what you can’t observe. The first investment is connector coverage broad enough to capture change events across the tools where work actually happens: source control, CI/CD, ticketing, chat, docs, calendars, observability, identity. Snapshot data is necessary but not sufficient. You need the event stream of changes over time.</p><p>This is harder than it sounds. Each tool has its own API, rate limits, and idea of what “changed” means. Identity reconciliation alone is its own minor industry. The same human is priya@example.com in Slack, priya.k@example.com in GitHub, and Priya Kumar (Engineering) in your HRIS. Until you can prove those are the same person, your aggregates lie to you.</p><h3>2. The semantic layer</h3><p>Before you put events in a graph, you need agreement on what entities mean. A deployment event from your CD tool, a release from your CI tool, and a change in your ITSM system might describe the same physical action, or three different ones. Without a canonical model, downstream queries are unreliable.</p><p>Harness’s framing is useful here: the semantic layer is the source of truth for the structure and meaning of the data, and it enforces consistent definitions across tools. This is not supporting infrastructure. It is the substrate the rest of the system depends on. Every aggregation, every query, and every agent decision downstream inherits its meaning from this layer. Get it wrong and the layers above don’t fail loudly; they produce confidently wrong output. You can implement it with formal ontologies, JSON Schema, protobuf, or a registry of resource types. The implementation choice is less consequential than the discipline of treating the canonical model as load-bearing and making every connector conform to it.</p><h3>3. Trace stitching and task segmentation</h3><p>Raw event streams are not directly useful. A given person edits a doc, switches to Slack, opens a PR, runs a build, comes back to the doc. Are those one task or three? The graph needs to carve continuous activity into bounded units of work.</p><p>The approaches that work in practice combine cheap signals (shared titles, links between artifacts, time windows, channel names) with an LLM step that looks at sequences of events and infers semantic boundaries: “this cluster looks like investigating an alert,” “these actions look like drafting a spec.” The output is a labeled task with a coarse type, a duration, and a set of entities touched.</p><p>The cheap signals do most of the work. The LLM step is for cases where the cheap signals disagree or run out. Reverse the order and you’ll burn a lot of tokens for marginal gains.</p><h3>4. Aggregation and pattern mining</h3><p>Once you have many personal task traces, you aggregate. Normalize each trace into a sequence of anonymized steps: action type, tool family, knowledge graph entities involved, derived process tags, lightweight timing. Compute similarity between traces. Group similar traces. Mine the most common paths.</p><p>The output is a probabilistic model: for situations of type X, the typical sequence is A, B, C with probabilities p1, p2, p3, and timing distributions T1, T2, T3.</p><p>The word “probabilistic” is doing real work in that sentence, and it’s worth pausing on. The graph is not canonical truth and shouldn’t be presented to downstream consumers as one. Real organizational processes are noisy, overlapping, partially observable, and constantly evolving. The same situation can resolve through three different paths depending on who’s on call, what week of the quarter it is, and which subsystem happened to fail first. A model that collapses that reality into a single deterministic process will quietly mislead the agents that consume it, and the failure mode is the worst kind: confident, plausible, wrong.</p><p>Good implementations carry the uncertainty forward rather than papering over it. Each inferred path gets a confidence score that reflects how well the underlying traces actually support it. Temporal weighting decays older traces so the model tracks recent reality instead of the org chart from six quarters ago. Competing paths for the same situation are kept as parallel hypotheses with their own probabilities, not collapsed into the highest-frequency one. Sparse situations are flagged as low-confidence rather than presented with false precision.</p><p>The agent consuming the graph then has to reason under that uncertainty: pick the most probable path when confidence is high, surface alternatives when it isn’t, and fall back to first-principles reasoning when the situation is novel enough that the graph has no strong signal. The graph’s job is to make the uncertainty legible to the agent above it, not to hide it behind a single most-likely answer.</p><p>The Glean blog calls out a useful constraint here: only treat a pattern as viable if it appears across at least <em>k</em> distinct users and <em>n</em> independent traces. Below that threshold, you’re modeling individuals, not processes, and you’re at real risk of leaking PII through deanonymization. Pick your <em>k</em> and <em>n</em> before you ship, not after.</p><h3>5. Storage that supports both structured and semantic access</h3><p>Pure graph stores are great for traversal but rigid for free-form text. Pure vector stores are great for semantic similarity but blind to structure. A hybrid approach is what most teams converge on: graph entities and edges for relationships, with text chunks tagged by entity IDs that get embedded for semantic search.</p><p>This is the same pattern that underpins KG + RAG systems more generally. The graph provides depth (relationships, lineage, ownership). The vector index provides breadth (matching free-form queries to relevant content). The semantic layer ties them together so that “the auth incident from yesterday” resolves to a specific node, not a set of approximately-relevant text fragments.</p><p><strong>PLACEHOLDER: hybrid storage diagram.</strong> Show two parallel stores at the bottom (a graph database with nodes/edges, and a vector index with embeddings). Above them, a query layer that takes a natural-language question, decomposes it, hits both stores, and returns a unified result. Show entity IDs as the join key between the two stores. Optional: a third lane showing “process traces” stored as ordered sequences referencing the same entity IDs.</p><h3>6. The feedback loop</h3><p>A context graph that stops learning is a static playbook with extra plumbing. The architecture only pays off if agent and human actions feed back in as new traces. When an agent runs a workflow, the outputs (which tools it called, in what order, with what result, whether the user accepted the output) become training signal. Successful runs reinforce the patterns. Failed runs flag anti-patterns where the model’s predicted path didn’t match reality.</p><p>This is where the system starts to get interesting from a reinforcement-learning angle. The graph functions as a policy the agent samples actions from, and that policy updates as the agent acts. The Glean piece makes a good operational point on this: if your agents run outside the system that owns the graph, the graph evolves one way and agent behavior evolves another. You end up with two divergent versions of reality. The graph and the orchestration layer have to share a feedback path.</p><h3>The OS analogy is more useful than it looks</h3><p>Sunil Gattupalle at Harness frames the agent loop as an operating system. The mapping is more than rhetorical, and it does real design work for context graphs too:</p><ul><li>The <strong>context window is RAM</strong>. Finite. Everything the agent can see right now lives here.</li><li><strong>Tool calls are syscalls.</strong> Structured I/O between the agent and the rest of the world.</li><li><strong>The agent harness (MCP server, framework, custom orchestrator) is the kernel.</strong> It mediates access to underlying systems and enforces policy.</li><li><strong>The knowledge and context graphs are the filesystem.</strong> Persistent structured storage the agent can query.</li><li><strong>Schema discovery is virtual memory.</strong> The agent doesn’t load every type definition upfront. It pages in metadata when it needs it.</li></ul><p>If you take that mapping seriously, the design constraints fall out cleanly. You don’t dump the whole context graph into the agent’s context window any more than you would cat the entire filesystem into RAM. You build query primitives that let the agent pull the slice it needs.</p><p>The Harness MCP redesign is a useful concrete example. The team went from 130+ endpoint-shaped tools to 11 generic verbs (list, get, create, update, execute, describe, diagnose, and a handful of others) backed by a registry that dispatches to the right resource type. The tool count stays constant; capability grows in the registry. Whatever your stack, the underlying lesson holds: keep the agent’s “menu” small, push capability into queryable backends, and let the agent reason about what to fetch rather than parse a giant tool catalog.</p><p>The same lesson applies to context graphs. You don’t expose 50 tools that each query one slice of the graph. You expose a small set of generic query verbs (describe_process, find_similar_traces, get_typical_path) and let the graph itself hold the variety.</p><p><strong>PLACEHOLDER: agent loop / OS mapping diagram.</strong> Two columns side by side. Left column: classic OS stack (process → syscalls → kernel → drivers → hardware). Right column: agent stack (LLM reasoning → tool calls → harness/kernel → resource registry → backend systems and graphs). Horizontal arrows showing the analogous components.</p><h3>Pitfalls worth watching</h3><p>Patterns that go wrong in real implementations:</p><p><strong>Storing events without entity resolution.</strong> If you can’t reliably say two different event streams are about the same logical entity, your aggregation is meaningless. Identity reconciliation is unglamorous and load-bearing.</p><p><strong>Treating the context graph as static.</strong> Process changes constantly. Tools come and go. Teams reorg. If your graph stops ingesting and re-aggregating, it ages out fast. Anything older than a quarter is suspect for active processes.</p><p><strong>Underinvesting in the semantic layer.</strong> Without a canonical model of what entities and actions mean, graph rot accelerates. New tools get integrated with ad-hoc mappings. Queries return inconsistent results. Engineers stop trusting it.</p><p><strong>Hard-coding workflows on top of the graph.</strong> The whole point is that the system learns process. If you turn around and embed a fixed playbook on every common path, you’ve built a regular workflow engine with extra plumbing.</p><p><strong>Ignoring k-anonymity in aggregation.</strong> Aggregate process insights derived from a small number of users are deanonymized personal graphs in disguise. Pick a threshold and enforce it before you ship.</p><p><strong>Letting context drift from execution.</strong> This is the divergence problem above. The graph that informs the agent and the system the agent acts in have to share a feedback path, or they will desynchronize within weeks.</p><p><strong>Loading the wrong thing into context.</strong> A 50KB process description in the agent’s working memory is 50KB you don’t have for reasoning. Design the graph’s query API to return small, focused slices. If the only way to use the graph is to dump it, the agent will degrade.</p><h3>How to evaluate one</h3><p>A common question once you have a context graph is whether it’s actually useful. Some signals worth tracking:</p><p><strong>Coverage.</strong> What fraction of meaningful work in your org is represented in the graph? If your most expensive processes aren’t in it, the graph isn’t helping where it matters.</p><p><strong>Path agreement.</strong> When an expert is asked to describe how X usually happens, does their description match the graph’s most probable path for X? This is a sanity check for trace stitching and aggregation. Disagreement is informative either way: either the graph is wrong, or the expert is describing the ideal rather than what really happens.</p><p><strong>Agent task completion.</strong> Agents grounded in the graph should complete relevant tasks at higher success rates than agents using only documentation or only tool descriptions. If they don’t, the graph is too noisy or too sparse, and you have a calibration problem upstream.</p><p><strong>Time-to-fresh.</strong> How long after a real-world process changes does the graph reflect it? If it takes weeks, you have built a museum, not a model.</p><p><strong>Context cost.</strong> What’s the average number of tokens an agent spends pulling relevant context from the graph for a given task? Track this over time. If it’s growing, your query API is leaking abstraction.</p><h3>A question I haven’t seen good answers to yet</h3><p>There isn’t a standard schema for context graphs the way there is for distributed traces (OpenTelemetry) or feature stores. Every team rolls their own. That’s fine for now because the design space is still being explored, but it makes federation across organizations and tools harder than it needs to be. The closest adjacent standard is OCSF for security events, which has the right shape but the wrong domain.</p><p>If you’re building a context graph today, document your schema with the same rigor you’d apply to a public API. Future-you, and any other team that integrates with your graph, will appreciate it.</p><h3>Closing</h3><p>Context graphs sit at the intersection of three things engineers have been shipping pieces of for years: knowledge graphs, activity streams, and agentic systems. The new and useful synthesis is the combination. A context graph captures how work actually happens, not just what exists, and it gives agents a structured, queryable model of process to ground their reasoning in.</p><p>Whether you call it a context graph, a process graph, a behavioral graph, or just an aggregate activity model, the design constraints are the same. Capture events at depth. Resolve entities to canonical forms. Stitch traces into tasks. Aggregate patterns under privacy thresholds. Store hybrid (graph plus vector). Treat agent execution as both consumer and producer of the graph. Keep the agent’s working memory clean.</p><p>The teams building this well aren’t reinventing graph databases. They’re applying old systems-engineering principles (small stable interfaces, demand paging, content-addressable storage, feedback loops) to a problem that’s only become tractable in the last couple of years.</p><h3>References</h3><ul><li>David Huynh and Pradeep Vaghela, “How do you build a context graph?”, Glean Engineering blog</li><li>Sunil Gattupalle, Rohan Gupta, and Shubham Jindal, “Architecting MCP for AI Agents: Lessons from Our Redesign”, Harness Engineering blog</li><li>Sunil Gattupalle, “The Agent Loop Is the New OS: Design Philosophy of the Harness MCP Server”, Harness Engineering blog</li><li>Sunil Gattupalle, “Knowledge Graph + RAG: A Unified Approach to DevOps Intelligence”, Harness Engineering blog</li></ul><p><em>Originally published at </em><a href="https://www.harness.io/blog/what-a-context-graph-actually-is-and-how-to-build-one"><em>https://www.harness.io</em></a><em> on May 18, 2026.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d3670f835505" width="1" height="1" alt=""><hr><p><a href="https://medium.com/harness-engineering/what-a-context-graph-actually-is-and-how-to-build-one-d3670f835505">What a Context Graph Actually Is, and How to Build One</a> was originally published in <a href="https://medium.com/harness-engineering">Harness Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Agent Loop Is the New OS: Design Philosophy of the Harness MCP Server]]></title>
            <link>https://medium.com/harness-engineering/the-agent-loop-is-the-new-os-c4db8876bc58?source=rss----c7a8b2228e23---4</link>
            <guid isPermaLink="false">https://medium.com/p/c4db8876bc58</guid>
            <dc:creator><![CDATA[Harness Engineering]]></dc:creator>
            <pubDate>Wed, 29 Apr 2026 01:17:36 GMT</pubDate>
            <atom:updated>2026-04-29T06:53:19.465Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ZtP0BVQsFvtDvcGA.jpeg" /></figure><p>Author: <a href="https://www.harness.io/authors/sunil-gattupalle">Sunil Gattupalle</a></p><p>The design of the <a href="https://www.harness.io/blog/harness-mcp-server-redesign">Harness MCP (Model Context Protocol) </a>server is driven by a pattern that keeps reappearing across systems that scale well: small, stable interfaces with most of the complexity pushed behind a dispatch layer. The central idea is this: <strong>the agent loop behaves like an operating system boundary</strong>. The LLM is the reasoning engine, the context window is working memory, tool calls act like syscalls, and the MCP server serves as a kernel that mediates access to underlying systems. This isn’t a literal equivalence, but it’s a useful design lens. It forces you to think in terms of memory pressure, interface stability, and clean I/O contracts.</p><p>We built the Harness MCP server to make Harness agent-native. In practice, that means exposing the platform through a runtime-discoverable, schema-driven interface that agents can inspect, select from, and compose without hardcoded knowledge of the domain. Today, that interface consists of 10 generic tools that dispatch to 30 toolsets covering 140+ resource types across the platform, along with 57 Knowledge Graph views for cross-module analytics.</p><p>Those numbers matter less than the constraint behind them: tool count stays constant while capability scales through data and dispatch. The goal is to keep the agent’s context focused on reasoning, not on parsing a large menu of endpoints.</p><p>Before getting into the architecture, though, it’s worth asking a simpler question: why does Claude feel so capable when you give it nothing more than a bash shell?</p><h3>Why Claude Feels Powerful with Just Bash</h3><h4>The Contrast</h4><p>Give Claude access to a terminal. Just bash. No APIs, no SDKs, no custom tools. It can navigate an unfamiliar codebase, find a bug across 50 files, refactor code, run tests, and commit end-to-end.</p><p>Now give an LLM access to a hundred perfectly-documented REST endpoints. It gets confused by the tool count, picks the wrong endpoint, and loses track of multi-step operations.</p><p>The difference isn’t the tools themselves. It’s the shape of the interface. The point isn’t that shell text streams are superior to structured APIs, but that agents perform better with interfaces that have a small, consistent grammar and are easy to compose.</p><h4>What Makes Bash Work</h4><p>Bash provides three properties that matter enormously for agent reasoning:</p><p>Composability. Every Unix tool does one thing and communicates through a uniform interface: text streams. grep | sort | uniq -c | head is four tools composed into an analytical pipeline. The agent doesn’t need to know about a special “count-unique-matches” API. It composes primitives.</p><p>Uniform interface. Every tool takes text in and produces text out. There’s no per-tool protocol, no per-tool authentication, no per-tool response schema. The contract is always the same: stdin, stdout, and exit code.</p><p>Introspection. ls, find, file, cat, head — the agent can discover what exists at runtime. It doesn’t need to memorize the file system layout. It explores, then acts.</p><p>These three properties mean the agent doesn’t need to hold 200 tool schemas in its context window. It learns a small set of verbs and composes them. The intelligence isn’t in any single tool. It’s in the loop that decides what to call next.</p><h4>The Deeper Insight</h4><p>Watch what actually happens when Claude debugs with bash:</p><pre>1. Observe:     ls src/                    → see the project structure<br>2. Hypothesize: &quot;error likely in auth module&quot;<br>3. Act:         grep -r &quot;token&quot; src/auth/<br>4. Observe:     see the grep output<br>5. Refine:      &quot;ah, token expiry not handled&quot;<br>6. Act:         cat src/auth/session.ts<br>7. Observe:     read the file<br>8. Fix:         edit the file<br>9. Verify:      npm test</pre><p>This is not “call the right API.” This is a reasoning loop — observe, hypothesize, act, verify. The bash commands are just I/O. The reasoning happens between them.</p><p>This loop is the program. The tools are the I/O. And the design of the tools determines how efficiently the loop can run.</p><h3>The Agent Loop — What’s Actually Happening</h3><h4>The Event Loop</h4><p>Every agent, whether it’s Claude in a terminal, Cursor with MCP tools, or a custom orchestrator, runs some version of this loop:</p><pre>while (!task_done) {    <br>context   = observe(environment)       // tool outputs, previous results    <br>plan      = reason(context, goal)      // LLM inference    <br>action    = select_tool(plan)          // tool selection    <br>result    = execute(action)            // tool call    <br>environment.update(result)             // state change<br>}</pre><p>This is an event loop. The LLM is the scheduler (the scheduling behavior is an emergent property of the loop, not an intrinsic property of the LLM). The tools are I/O operations. The context window is working memory. Each iteration, the agent observes the current state, reasons about what to do next, selects a tool, executes it, and incorporates the result into its context.</p><h4>Where the Intelligence Lives</h4><p>The critical insight: the intelligence is in the loop, not in the tools. The tools just move information in and out. The loop is what plans, backtracks, retries, composes, and converges.</p><p>This means the quality of the agent’s output depends on two things:</p><ol><li>The loop’s ability to reason, which is a function of the LLM’s capability and the available context</li><li>The quality of I/O, which is a function of tool design (clear, structured, context-efficient)</li></ol><p>If the tools are well-designed (few, composable, self-describing, context-efficient), the loop can reason clearly. If the tools are poorly designed (many, verbose, opaque), the loop spends its context budget parsing menus and payloads instead of thinking.</p><h4>The MCP Server’s Role</h4><p>Our MCP server does not implement the agent loop. The loop lives in the MCP host: Cursor, Claude Desktop, or whatever IDE/agent framework the user is running. Our server is stateless at the request level: each tool call arrives as a JSON-RPC message, runs an async handler, and returns a structured response. Task-level state lives in the MCP host and in the underlying Harness systems.</p><p>We implement the kernel that the loop dispatches into. Our job is to make each dispatch fast, clean, and context-efficient.</p><h3>Systems That Just Click: We’ve Seen This Pattern Before</h3><p>Before drawing the OS analogy, it’s worth stepping back. The properties that make bash work for agents, composability, uniform interface, and runtime discovery, aren’t unique to Unix. They show up in every long-lived system that engineers describe as “just working.”</p><p><strong>Linux:</strong> The syscall ABI has been stable for decades. The VFS (Virtual File System) is a dispatch table: open(), read(), write(), close() work against ext4, NFS, procfs, sysfs, and any backend. New filesystem? Write a driver, load it at runtime. The interface never changes. /proc and /sys let the kernel describe itself through runtime introspection.</p><p><strong>Git:</strong> Content-addressable blobs plus a handful of verbs. Branches are just pointers. The plumbing/porcelain split gives you a tiny, stable core with everything else built through composition. The transport protocol is uniform: push/fetch work the same over HTTP, SSH, or a local filesystem.</p><p><strong>Kubernetes:</strong> Declare desired state. Controllers reconcile. kubectl get, apply, describe work on any resource kind: Pods, Services, your custom CRDs. New capability = new CRD, not a new CLI.</p><p><strong>SQL:</strong> Small grammar: SELECT, JOIN, WHERE, GROUP BY. Works against any schema. The engine optimizes. You declare intent. The grammar has been stable for 40 years.</p><h4>The Shared DNA</h4><p>These systems share five properties:</p><ol><li>Small, stable interface: few verbs, many nouns</li><li>Uniform contract: same interaction pattern regardless of backend</li><li>Runtime discovery: the system describes itself</li><li>Dispatch layer: a central mechanism that routes requests to domain-specific handlers</li><li>Extend without changing the core interaction model: new capabilities don’t require changing the existing interface.</li></ol><p>This is the design target for agent infrastructure.</p><h3>The Gap in REST: What MCP Had to Fill</h3><p>REST APIs answered two questions well:</p><ul><li>WHAT: what resources exist (/pipelines, /services, /executions)</li><li>HOW: how to operate on them (GET, POST, PUT, DELETE)</li></ul><p>For programs, code written by humans who already understood the domain, this was enough.</p><p>The developer read the docs, wrote the integration, and deployed it. The logic was pre-written.</p><p>An agent encounters your API at runtime, with no prior knowledge. It needs a third answer:</p><p>WHY:</p><ul><li>Why would I use this tool?</li><li>What does it return?</li><li>When should I prefer it over another tool?</li><li>What are the side effects?</li></ul><p>This “why” lived in documentation, READMEs, and developers’ heads. It was never machine-readable. MCP fills this gap by making tools carry their own intent — descriptions, hints (readOnlyHint, destructiveHint), schemas, and metadata that the agent reads at runtime to decide what to call.</p><p>The difference between REST and MCP isn’t the transport. It’s the audience. REST APIs are typically optimized for pre-written integrations. MCP tool surfaces are optimized for runtime selection and composition by an agent.</p><h3>The OS Analogy: A Systems Engineer’s Guide to Agent Platforms</h3><p>The mapping between operating systems and agent platforms is more than metaphorical — parts of it are structural, and the rest provide a useful design vocabulary. The same engineering constraints apply, and the same design principles solve them.</p><h4>The Full Mapping</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TKq1y0TNTAzT5A5Q3SLvgA.png" /></figure><h4>Context Is RAM</h4><p>This is the most important mapping, and it has direct engineering consequences.</p><p>The context window is finite. Every token you put in is a token that can’t be used for something else. Verbose API responses, unnecessary fields, large tool schemas: these are all memory allocations. If you fill the context with data, the agent can’t reason.</p><p>The OS parallels are precise:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WTH1oS_FVTEmcP5zjnB2ag.png" /></figure><p>The #1 job of an agent platform is to keep the context window clean for reasoning. Every architectural decision should be evaluated through this lens: does this consume more or less of the context budget?</p><h4>Syscalls, Not Raw I/O</h4><p>Programs don’t write directly to disk. They call write(), and the kernel handles buffering, permissions, journaling, and device-specific quirks. This abstraction is what makes programs portable and reliable.</p><p>The same applies to agents. An agent shouldn’t construct HTTP requests with auth headers, manage pagination cursors, handle retry backoff, or parse nested response wrappers. It should call a tool — a syscall — and the MCP server (the kernel) handles all of that.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ZSKiKkqge4MvWyYi.png" /></figure><p>The tool is the syscall. The MCP server is the kernel. Same contract every time. The agent never has to think about x-api-key headers, accountIdentifier query parameters, or exponential backoff on HTTP 429.</p><h4>Virtual Memory = Schema Discovery</h4><p>An OS doesn’t load every file into RAM upfront. It uses virtual memory — a large address space backed by on-demand paging. Hot pages stay in RAM; cold pages live on disk until needed.</p><p>Our MCP server applies the same pattern to domain knowledge. The agent’s “address space” covers 140+ resource types. But at any given moment, only the relevant metadata occupies context:</p><ul><li>Need pipeline fields? → harness_describe(resource_type=’pipeline’) loads ~500 tokens</li><li>Need execution details? → harness_describe(resource_type=’pipeline_execution’) loads ~3K tokens of type catalog</li><li>Need pipeline schema? → harness_schema(resource_type=’pipeline’), loaded only when constructing pipeline, and enables progressive discovery</li></ul><p>This is demand paging for domain knowledge. The agent discovers what it needs, when it needs it, and the rest stays “on disk” (available but not occupying context).</p><h3>Our Architecture Through This Lens</h3><h4>The Three-Layer Stack</h4><p>The MCP server has three layers, each corresponding to a layer in the OS model:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*IyCX9YfJzhRpwv3y.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*5KbjDna-LqQdjEo8.png" /></figure><p>Harness MCP Server Architecture</p><p><strong>Layer 1</strong> — MCP Tool Surface (syscall table). Ten generic tools that accept a resource_type parameter and dispatch through the registry. These are registered with the MCP SDK using Zod schemas for input validation. Each tool handler is a thin wrapper: normalize inputs → call registry → format response.</p><p><strong>Layer 2</strong> — Registry (kernel). The Registry class in src/registry/index.ts is the core dispatch engine. It holds a Map&lt;string, ResourceDefinition&gt; populated from 30 toolset files. When a tool handler calls registry.dispatch(client, resourceType, operation, input), the registry resolves the ResourceDefinition, looks up the EndpointSpec, and calls executeSpec() — the single execution pipeline that handles path templating, scope injection, query parameter building, body construction, auth header interpolation, HTTP dispatch, response extraction, and deep link generation.</p><p><strong>Layer 3</strong> — HarnessClient (block device driver). The raw HTTP client in src/client/harness-client.ts. Handles fetch() with the x-api-key auth header, accountIdentifier injection, retry with exponential backoff on 429/5xx, client-side rate limiting, timeouts, and response parsing.</p><h4>The Syscall Table</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*alJW8scF4Z8OQ_gGcMQb1w.png" /></figure><p>The agent learns 10 verbs. They work against every domain in Harness.</p><h4>Why Tool Count Matters</h4><p>Every tool the agent “sees” costs tokens:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PqxFyxXedtzUirR9eXzXJA.png" /></figure><p>Our approach keeps this at ~1.2%. Tool count stays O(1). Capabilities grow O(n). This is the core design invariant.</p><h4>The Dispatch Table (vtable)</h4><p>The registry is a vtable — a dispatch table that maps (resource_type, operation) to an EndpointSpec and executes it through a unified pipeline. One execution path. Every resource type. Every operation.</p><h3>Design Patterns</h3><h4>Pattern 1: Generic Verbs + Type Dispatch</h4><p>Principle: Don’t create a tool per API endpoint. Create generic verbs that dispatch by resource type through a registry.</p><p>This is the same insight behind REST (uniform interface + varying resources) and Unix (uniform file interface + varying devices). The agent learns the grammar once — list, get, create, execute. New nouns (resource types) are just data in the registry.</p><h4>Pattern 2: Declarative Over Imperative</h4><p>Principle: Resource definitions are data structures, not handler functions.</p><p>Each API mapping is expressed as an EndpointSpec — a declarative object that describes the HTTP method, path, path parameters, query parameter mappings, body builder, response extractor, and metadata. The registry’s executeSpec() reads this spec and handles execution.</p><ul><li>Auth, retry, pagination, rate limiting, deep links — all handled once in the registry</li><li>New resource types get these behaviors automatically</li><li>Changes to the execution pipeline propagate to every resource type</li></ul><p>This means:</p><h4>Pattern 3: Fix Once in the Kernel, Every Driver Benefits</h4><p>Principle: Centralized dispatch creates compounding returns on infrastructure investment.</p><p>Features that propagate everywhere through the registry:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FKB0e3B_w1xRuGW9kMeFUA.png" /></figure><h4>Pattern 4: Validate Before Execute</h4><p>Move error detection left. Validate agent-generated inputs before spending API budget on execution.</p><p>When an agent tries to create or execute something, validate the inputs before committing. If the agent provides a malformed pipeline YAML or references a nonexistent service, catch it at the schema level — before the API call burns tokens on a 400 error and the agent has to parse the response to figure out what went wrong.</p><pre>harness_execute(<br>resource_type=&#39;pipeline&#39;, action=&#39;run&#39;, <br>inputs={branch: &#39;main&#39;, service: &#39;payment-svc&#39;}<br>) <br>← Error: input &#39;service&#39; is not a valid runtime input for this pipeline.    <br>Valid inputs: branch, environment, tag. Did you mean &#39;environment&#39;?<br>// Agent retries with corrected inputs. Typically converges in one retry.</pre><p>Validation is cheap — milliseconds. Wrong answers are expensive — broken trust, bad decisions. This is compile-time checking for agent-generated operations.</p><h4>Pattern 5: Runtime Reflection Over Static Documentation</h4><p>Principle: Let agents discover your domain model at runtime. Self-describing systems don’t need documentation updates.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*H0vTqjhiccsY1vbI6nKrVA.png" /></figure><p>Add a new toolset → the agent discovers it immediately. Add a new resource type → the agent can query it immediately. This is introspection — ls for your platform. The same thing that makes bash work for agents.</p><h3>Anti-Patterns</h3><h4>Anti-Pattern 1: One Tool Per Endpoint</h4><p>Problem: Creating list_pipelines, get_pipeline, list_services, etc. Each tool costs ~150 tokens. At 50 tools, that’s 7,500 tokens of menu.</p><p>Fix: Generic verbs with type dispatch.</p><h4>Anti-Pattern 2: Raw API Passthrough</h4><p>Problem: Returning the full Harness API response — 50+ fields, nested wrappers.</p><p>Fix: Use responseExtractor to return clean, relevant fields. Treat context tokens like memory allocations.</p><h4>Anti-Pattern 3: Hardcoded Domain Knowledge</h4><p>Problem: Embedding field lists or API shapes in tool descriptions. They go stale immediately.</p><p>Fix: Keep tool descriptions generic. Point to harness_describe() for runtime discovery.</p><h4>Anti-Pattern 4: Context-Window Aggregation</h4><p>Problem: Agents often fetch large datasets and aggregate in context, leading to extremely high token usage and degraded accuracy.</p><p>Fix: Routing aggregation to the Knowledge Graph dramatically reduces token usage and improves answer reliability.</p><h4>Anti-Pattern 5: Bloating Server Instructions</h4><p>Problem: Adding per-resource docs to instructions in src/index.ts.</p><p>Fix: Keep instructions under ~20 lines. Put resource-specific guidance in description, diagnosticHint, executeHint, and bodySchema.description on the EndpointSpec.</p><h4>Conclusion</h4><p>The agent loop is the new operating system. That’s not a rhetorical flourish. It’s a constraint with real engineering consequences.</p><p>Every design decision in the Harness MCP server follows from a single principle: the context window is RAM, and RAM is finite. Verbose responses trash it. Oversized tool menus fragment it. Redundant schemas waste it. The agent’s ability to reason, to observe, hypothesize, act, and verify, degrades in direct proportion to how much of that budget gets consumed by infrastructure noise instead of domain signal.</p><p>The patterns described here, generic verbs with type dispatch, declarative resource definitions, demand-paged schema discovery, and centralized kernel dispatch, aren’t novel. They’re the same patterns that made Unix, Git, Kubernetes, and SQL endure for decades: small, stable interfaces, uniform contracts, runtime introspection, and the ability to extend without changing the core interaction model.</p><p>What’s different is the audience. Those systems were designed for programs. This one is designed for reasoning systems operating at runtime.</p><p>If you’re building agent infrastructure, the questions to ask are the same ones OS designers asked in the 1970s: Does this abstraction compose? Does it describe itself? Does it keep the critical resource, then RAM, now context, available for the work that actually matters?</p><p>A useful test for any tool, schema, or abstraction is simple: does it reduce the amount of information the agent has to hold in working memory, or increase it? If it increases it, it’s probably making the system worse.</p><p>-</p><p>If you found this useful, follow and subscribe to the <a href="https://medium.com/harness-engineering">Harness Engineering blog</a> for more deep dives on building agent-native systems and modern developer infrastructure.</p><p>‍</p><p><em>Originally published at </em><a href="https://www.harness.io/blog/agent-loop-new-os"><em>https://www.harness.io</em></a><em> on April 23, 2026.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c4db8876bc58" width="1" height="1" alt=""><hr><p><a href="https://medium.com/harness-engineering/the-agent-loop-is-the-new-os-c4db8876bc58">The Agent Loop Is the New OS: Design Philosophy of the Harness MCP Server</a> was originally published in <a href="https://medium.com/harness-engineering">Harness Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Theory to Turbulence: Building a Developer-Friendly E2E Testing Framework for Chaos Platform]]></title>
            <link>https://medium.com/harness-engineering/theory-to-turbulence-building-a-developer-friendly-e2e-testing-framework-for-chaos-platform-98d403645476?source=rss----c7a8b2228e23---4</link>
            <guid isPermaLink="false">https://medium.com/p/98d403645476</guid>
            <dc:creator><![CDATA[Harness Engineering]]></dc:creator>
            <pubDate>Fri, 03 Apr 2026 04:56:35 GMT</pubDate>
            <atom:updated>2026-04-03T04:56:35.473Z</atom:updated>
            <content:encoded><![CDATA[<p>Author: <a href="http://www.harness.io/authors/vedant-shrotria">Vedant Shrotria</a></p><p>As an enterprise chaos engineering platform vendor, validating chaos faults is not optional — it’s foundational. Every fault we ship must behave predictably, fail safely, and produce measurable impact across real-world environments.</p><p>When we began building our end-to-end (E2E) testing framework, we quickly ran into a familiar problem: <strong>the barrier to entry was painfully high</strong>.</p><p>Running even a single test required a long and fragile setup process:</p><ul><li>Installing multiple dependencies by hand</li><li>Configuring a maze of environment variables</li><li>Writing YAML-based chaos experiments manually</li><li>Debugging cryptic validation failures</li><li>Only then… executing the first test</li></ul><p>This approach slowed feedback loops, discouraged adoption, and made iterative testing expensive — exactly the opposite of what chaos engineering should enable.</p><h3>The Solution: A Simplified Chaos Fault Validation Framework</h3><p>To solve this, we built a <strong>comprehensive yet developer-friendly E2E testing framework</strong> for chaos fault validation. The goal was simple: <strong>reduce setup friction without sacrificing control or correctness</strong>.</p><p>The result is a framework that offers:</p><ul><li>An <strong>API-driven execution model</strong> instead of manual YAML wiring</li><li><strong>Real-time log streaming</strong> for faster debugging and observability</li><li><strong>Intelligent target discovery</strong> to eliminate repetitive configuration</li><li><strong>Dual-phase validation</strong> to verify both fault injection and system impact</li></ul><p>What previously took 30 minutes (or more) to set up and run can now be executed in under 5 minutes — consistently and at scale.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/700/0*Mj1N-spEkQNH2RVf.png" /></figure><p><em>A real execution run — proving that chaos validation doesn’t have to be chaotic — </em><strong><em>From theory to turbulence</em></strong></p><h3>System Architecture</h3><h3>High-Level Architecture</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/700/0*pntfFCwTI8Uttlyn.png" /></figure><p>High-level architecture</p><h3>Layer Responsibilities</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/700/0*JC_F3d0htrmN86Z9.png" /></figure><h3>Core Components</h3><h3>1. Experiment Runner</h3><p>Purpose: <em>Orchestrates the complete chaos experiment lifecycle from creation to validation.</em></p><p><strong>Key Responsibilities:</strong></p><ul><li>Experiment creation with variable substitution</li><li>Log streaming and target discovery</li><li>Concurrent validation management</li><li>Status monitoring and completion detection</li><li>Error handling and retry logic</li></ul><p><strong><em>Architecture Pattern</em></strong><em>: Template Method + Observer</em></p><pre>type ExperimentRunner struct {<br>    identifiers utils.Identifiers<br>    config      ExperimentConfig<br>}<br>type ExperimentConfig struct {<br>    Name                  string<br>    FaultName             string<br>    ExperimentYAML        string<br>    InfraID               string<br>    InfraType             string<br>    TargetNamespace       string<br>    TargetLabel           string<br>    TargetKind            string<br>    FaultEnv              map[string]string<br>    Timeout               time.Duration<br>    SkipTargetDiscovery   bool<br>    ValidationDuringChaos ValidationFunc<br>    ValidationAfterChaos  ValidationFunc<br>    SamplingInterval      time.Duration<br>    }</pre><p><strong>Execution Flow:</strong></p><pre>Run() → <br>  1. getLogToken()<br>  2. triggerExperimentWithRetry()<br>  3. Start experimentMonitor<br>  4. extractStreamID()<br>  5. getTargetsFromLogs()<br>  6. runValidationDuringChaos() [parallel]<br>  7. waitForCompletion()<br>  8. Validate ValidationAfterChaos</pre><h3>2. Experiment Monitor</h3><p><strong>Purpose</strong>: <em>Centralized experiment status tracking with publish-subscribe pattern.</em></p><p><strong>Architecture Pattern</strong>: Observer Pattern</p><pre>type experimentMonitor struct {<br>    experimentID string<br>    runResp      *experiments.ExperimentRunResponse<br>    identifiers  utils.Identifiers<br>    stopChan     chan bool<br>    statusChan   chan string<br>    subscribers  []chan string<br>}</pre><p><strong>Key Methods:</strong></p><ul><li>start(): Begin monitoring (go-routine)</li><li>subscribe(): Create subscriber channel</li><li>broadcast(status): Notify all subscribers</li><li>stop(): Signal monitoring to stop</li></ul><p><strong>Benefits:</strong></p><ul><li>80% reduction in API calls</li><li>92% faster failure detection</li><li>Single source of truth</li><li>Easy to add new consumers</li></ul><h3>3. Validation Framework</h3><p><strong>Purpose</strong>: <em>Dual-phase validation system for concrete chaos impact verification.</em></p><h3>ValidationDuringChaos</h3><ul><li>Runs in parallel during experiment</li><li>Continuous sampling at configurable intervals</li><li>Stops when validation passes</li><li>Use case: Verify active fault impact</li></ul><h3>ValidationAfterChaos</h3><ul><li>Runs once after experiment completes</li><li>Single execution for final state</li><li>Use case: Verify recovery and cleanup</li></ul><h3>Function Signature:</h3><pre>type ValidationFunc func(targets []string, namespace string) (bool, error)<br>// Returns: (passed bool, error)</pre><h3>Sample Validation Categories:</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/700/0*bq0X8RxeMjYbZvvN.png" /></figure><h3>Experiment Execution Engine</h3><h3>Execution Phases</h3><pre>Phase 1: Setup<br>├─ Load configuration<br>├─ Authenticate with API<br>└─ Validate environment</pre><pre>Phase 2: Preparation<br>├─ Get log stream token<br>├─ Resolve experiment YAML path<br>├─ Substitute template variables<br>└─ Create experiment via API</pre><pre>Phase 3: Execution<br>├─ Trigger experiment run<br>├─ Start status monitor<br>├─ Extract stream ID<br>└─ Discover targets from logs</pre><pre>Phase 4: Validation (Concurrent)<br>├─ Validation During Chaos (parallel)<br>│  ├─ Sample at intervals<br>│  ├─ Check fault impact<br>│  └─ Stop when passed/completed<br>└─ Wait for completion</pre><pre>Phase 5: Post-Validation<br>├─ Validation After Chaos<br>├─ Check recovery<br>└─ Final assertions</pre><pre>Phase 6: Cleanup<br>├─ Stop monitor<br>├─ Close channels<br>└─ Log results</pre><h3>State Machine</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/700/0*L8os9Ek7RFCSnVxv.png" /></figure><h3>Concurrency Model</h3><pre>Main Thread:<br>├─ Create experiment<br>├─ Start monitor goroutine<br>├─ Start target discovery goroutine<br>├─ Start validation goroutine [if provided]<br>└─ Wait for completion<br>Monitor Goroutine:<br>├─ Poll status every 5s<br>├─ Broadcast to subscribers<br>└─ Stop on terminal status<br>Target Discovery Goroutine:<br>├─ Subscribe to monitor<br>├─ Poll for targets every 5s<br>├─ Listen for failures<br>└─ Return when found or failed<br>Validation Goroutine:<br>├─ Subscribe to monitor<br>├─ Run validation at intervals<br>├─ Listen for completion<br>└─ Stop when passed or completed</pre><h3>API Integration Layer</h3><h3>API Client Architecture</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/700/0*gGlk-Rw4FgixUVHU.png" /></figure><h3>Variable Substitution System</h3><p><strong>Template Format</strong>: {{ VARIABLE_NAME }}</p><p><strong>Built-in Variables:</strong></p><pre>INFRA_NAMESPACE          // Infrastructure namespace<br>FAULT_INFRA_ID          // Infrastructure ID (without env prefix)<br>EXPERIMENT_INFRA_ID     // Full infrastructure ID (env/infra)<br>TARGET_WORKLOAD_KIND    // deployment, statefulset, daemonset<br>TARGET_WORKLOAD_NAMESPACE // Target namespace<br>TARGET_WORKLOAD_NAMES   // Specific workload names (or empty)<br>TARGET_WORKLOAD_LABELS  // Label selector<br>EXPERIMENT_NAME         // Experiment name<br>FAULT_NAME              // Fault type<br>TOTAL_CHAOS_DURATION    // Duration in seconds<br>CHAOS_INTERVAL          // Interval between chaos actions<br>ADDITIONAL_ENV_VARS     // Fault-specific environment variables</pre><p><strong>Custom Variables</strong>: Passed via FaultEnv map in ExperimentConfig.</p><h3>Validation Framework</h3><h3>Architecture</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/700/0*AubYCGg52GC3Vc-u.png" /></figure><h3>Validation Categories</h3><p><strong>1. Resource Validators</strong></p><pre>ValidatePodCPUStress(targets, namespace) (bool, error)<br>ValidatePodMemoryStress(targets, namespace) (bool, error)<br>ValidateDiskFill(targets, namespace) (bool, error)<br>ValidateIOStress(targets, namespace) (bool, error)</pre><p><strong>Detection Logic:</strong></p><ul><li>CPU: Usage &gt; baseline + 30%</li><li>Memory: Usage &gt; baseline + 20%</li><li>Disk: Usage &gt; 80%</li><li>I/O: Read/write operations elevated</li></ul><p><strong>2. Network Validators</strong></p><pre>ValidateNetworkLatency(targets, namespace) (bool, error)<br>ValidateNetworkLoss(targets, namespace) (bool, error)<br>ValidateNetworkCorruption(targets, namespace) (bool, error)</pre><p><strong>Detection Methods:</strong></p><ul><li>Ping latency measurements</li><li>Packet loss percentage</li><li>Checksum errors</li></ul><p><strong>3. Pod Lifecycle Validators</strong></p><pre>ValidatePodDelete(targets, namespace) (bool, error)<br>ValidatePodRestarted(targets, namespace) (bool, error)<br>ValidatePodsRunning(targets, namespace) (bool, error)</pre><p><strong>Verification:</strong></p><ul><li>Pod age comparison</li><li>Restart count increase</li><li>Ready status check</li></ul><p><strong>4. Application Validators</strong></p><pre>ValidateAPIBlock(targets, namespace) (bool, error)<br>ValidateAPILatency(targets, namespace) (bool, error)<br>ValidateAPIStatusCode(targets, namespace) (bool, error)<br>ValidateFunctionError(targets, namespace) (bool, error)</pre><p><strong>5. Redis Validators</strong></p><pre>ValidateRedisCacheLimit(targets, namespace) (bool, error)<br>ValidateRedisCachePenetration(targets, namespace) (bool, error)<br>ValidateRedisCacheExpire(targets, namespace) (bool, error)</pre><p><strong>Direct Validation</strong>: Executes redis-cli INFO in pod, parses metrics</p><h3>Validation Best Practices</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/700/0*8wLtYQTXBp_Mt9Yn.png" /></figure><h3>Data Flow &amp; Lifecycle</h3><h3>Complete Experiment Lifecycle</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/700/0*agX2OzjAAsEqLwYn.png" /></figure><h3>Data Structures Flow</h3><pre>// Input<br>ExperimentConfig<br>    ↓<br>// API Creation<br>ExperimentPayload (JSON)<br>    ↓<br>// API Response<br>ExperimentResponse {ExperimentID, Name}<br>    ↓<br>// Run Request<br>ExperimentRunRequest {NotifyID}<br>    ↓<br>// Run Response<br>ExperimentRunResponse {ExperimentRunID, Status, Nodes}<br>    ↓<br>// Log Streaming<br>StreamToken + StreamID<br>    ↓<br>// Target Discovery<br>[]string (target pod names)<br>    ↓<br>// Validation<br>ValidationFunc(targets, namespace) → (bool, error)<br>    ↓<br>// Final Result<br>Test Pass/Fail with error details</pre><h3>Performance &amp; Scalability</h3><h3>Performance Metrics</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/700/0*1Yp4idvdU1JUzgzN.png" /></figure><h3>Concurrent Test Execution</h3><ul><li>Each test gets isolated namespace</li><li>Separate experiment instances</li><li>No shared state between tests</li><li>Parallel execution supported</li></ul><h3>Example Usage of Framework</h3><pre>RunExperiment(ExperimentConfig{<br>    Name: &quot;CPU Stress Test&quot;,<br>    FaultName: &quot;pod-cpu-hog&quot;,<br>    InfraID:         infraID,<br>    ProjectID:       projectId,<br>    TargetNamespace: targetNamespace,<br>    TargetLabel:     &quot;app=nginx&quot;, // Customize based on your test app<br>    TargetKind:      &quot;deployment&quot;,<br>    FaultEnv: map[string]string{<br>     &quot;CPU_CORES&quot;:            &quot;1&quot;,<br>     &quot;TOTAL_CHAOS_DURATION&quot;: &quot;60&quot;,<br>     &quot;PODS_AFFECTED_PERC&quot;:   &quot;100&quot;,<br>     &quot;RAMP_TIME&quot;:            &quot;0&quot;,<br>    },<br>    Timeout:          timeout,<br>    SamplingInterval: 5 * time.Second, // Check every 5 seconds during chaos<br>    <br>    // Verify CPU is stressed during chaos<br>    ValidationDuringChaos: func(targets []string, namespace string) (bool, error) {<br>         clientset, err := faultcommon.GetKubeClient()<br>         if err != nil {<br>          return false, err<br>         }<br>         return validations.ValidatePodCPUStress(clientset, targets, namespace)<br>    },<br>    <br>    // Verify pods recovered after chaos<br>    ValidationAfterChaos: func(targets []string, namespace string) (bool,error) {<br>        clientset, err := faultcommon.GetKubeClient()<br>        if err != nil {<br>         return false, err<br>        }<br>        return validations.ValidateTargetAppsHealthy(clientset, targets, namespace)<br>    },<br>})</pre><h3>Knowledge Sharing and Learning</h3><p>While this framework is proprietary and used internally, we believe in sharing knowledge and best practices. The patterns and approaches we’ve developed can help other teams building similar testing infrastructure:</p><h3>Key Takeaways for Your Team</h3><p><strong>Whether you’re building a chaos engineering platform, testing distributed systems, or creating any complex testing infrastructure, these principles apply:</strong></p><ol><li><strong>Measure your baseline</strong> — Know how long things take today</li><li><strong>Set ambitious goals</strong> — 10x improvements are possible</li><li><strong>Prioritize DX</strong> — Developer experience drives adoption</li><li><strong>Automate ruthlessly</strong> — Eliminate manual steps</li><li><strong>Share your learnings</strong> — Help others avoid the same pitfalls</li><li><strong><em>Collect user feedback</em></strong></li><li><strong><em>Celebrate improvements!</em></strong></li></ol><p>We hope these insights help you build better testing infrastructure for your team!</p><p><strong>Questions? Feedback? Ideas?</strong> <a href="https://join-community-slack.harness.io/">Join Harness community</a>. We’d love to hear about your testing challenges and how you’re solving them!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=98d403645476" width="1" height="1" alt=""><hr><p><a href="https://medium.com/harness-engineering/theory-to-turbulence-building-a-developer-friendly-e2e-testing-framework-for-chaos-platform-98d403645476">Theory to Turbulence: Building a Developer-Friendly E2E Testing Framework for Chaos Platform</a> was originally published in <a href="https://medium.com/harness-engineering">Harness Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Terraform License Change: BSL vs Open Source Guide]]></title>
            <link>https://medium.com/harness-engineering/terraform-license-change-bsl-vs-open-source-guide-1cf4b397c632?source=rss----c7a8b2228e23---4</link>
            <guid isPermaLink="false">https://medium.com/p/1cf4b397c632</guid>
            <dc:creator><![CDATA[Harness Engineering]]></dc:creator>
            <pubDate>Fri, 03 Apr 2026 04:53:23 GMT</pubDate>
            <atom:updated>2026-04-03T04:53:24.795Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*stOvjxmMYwDnj-9P.png" /></figure><p>Author: <a href="https://www.harness.io/authors/richard-black">Richard Black</a></p><p>Picture this. What should have been a routine Infrastructure as Code upgrade suddenly triggers a legal review, a compliance escalation, and an awkward platform meeting nobody planned for. Just the kind of chain reaction the <a href="https://www.harness.io/harness-devops-academy/what-is-terraform?tag=true">Terraform</a> license change set off for many teams, often long after the technical work was already done.</p><p>When HashiCorp announced its move from the Mozilla Public License v2.0 (MPL 2.0) to the Business Source License (BSL) in August 2023, the reaction extended beyond ‘just noise’ pretty quickly. It exposed how deeply Terraform had been assumed to be “safe, open, and predictable” infrastructure glue. Overnight, teams who thought they were managing purely technical risk found themselves pulled into licensing discussions they were never set up to handle.</p><p>The question quickly shifted from what changed to what does this mean for how we run infrastructure going forward?</p><h3>Understanding the Terraform BSL License and What Changed</h3><p>Before August 2023, Terraform’s open source MPL 2.0 license allowed broad reuse, modification, and redistribution, including for commercial services. This model made Terraform an easy default for internal platforms, consultancies, and SaaS providers building <a href="https://www.harness.io/products/infrastructure-as-code-management?tag=true">IaC</a>-powered offerings. It removed friction, and over time, that frictionless adoption became an assumption.</p><p>The Business Source License changes that calculus. Under the Terraform BSL license, use is still permitted for many internal scenarios, but explicitly restricted when Terraform is used to build competing commercial services. That distinction matters more than it first appears.</p><p>If you are:</p><ul><li>Operating an internal platform team offering IaC as a shared service.</li><li>Running managed infrastructure for customers.</li><li>Embedding Terraform into a commercial product.</li></ul><p>You are no longer operating in a clearly open space.</p><p>The HashiCorp license change paired legal challenges with uncertainty. Legal and compliance teams dislike ambiguity, and infrastructure teams rarely want to be the ones explaining why a core automation tool might fall into a gray area six months from now.</p><p>This is the context in which the BSL vs open source discussion actually matters: not ideology, but operational predictability.</p><h3>OpenTofu vs Terraform: Why an Open Baseline Matters</h3><p>In response to the license change, the community did what it has done many times before: it forked. OpenTofu emerged as a continuation of the last MPL-licensed Terraform release, governed under the Linux Foundation with an explicit commitment to <strong>open</strong>, <strong>community-driven development</strong>.</p><p>From a practical perspective, OpenTofu started life as a drop-in replacement. Existing configurations, state files, providers, and modules continued to work. That was intentional.</p><p>The goal was continuity without legal ambiguity.</p><p>Now, you and your team might ask, what makes OpenTofu compelling aside from feature novelty? And the biggest factor is governance clarity. OpenTofu provides an open source IaC tool whose roadmap, licensing, and contribution model are not tied to a single vendor’s commercial strategy. For organizations designing or evolving IaC platforms today, that matters…a lot.</p><p>Terraform, meanwhile, remains a powerful and widely adopted tool, particularly for teams already invested in Terraform Cloud or enterprise workflows. But its direction is now explicitly commercial-first. That does not make it wrong, but it does mean it is no longer the neutral baseline it once was.</p><p>This distinction is subtle but important. <strong>OpenTofu increasingly represents the default open path</strong>, while Terraform becomes a contextual choice driven by existing investment rather than long-term openness.</p><h3>Common Pitfalls When Evaluating Terraform Alternatives</h3><p>One of the biggest mistakes teams make after the Terraform license change is treating the decision as a simple binary swap. Replace the binary, update a few pipelines, move on. That approach works briefly, and then fails in production.</p><p>Migrating from Terraform requires more than CLI compatibility:</p><ul><li>Provider versions need validation.</li><li>Module registries need auditing.</li><li>State backends and locking mechanisms must be reviewed.</li><li><a href="https://www.harness.io/products/continuous-integration?tag=true">CI</a>/<a href="https://www.harness.io/products/continuous-delivery?tag=true">CD</a> pipelines often hardcode assumptions that surface only under load.</li></ul><p>Another common misstep is assuming ecosystem maturity is static. Terraform benefits from years of accumulated modules and institutional knowledge. OpenTofu is closing that gap quickly, but teams still need to inventory dependencies rather than assume universal parity.</p><p>Finally, many organizations ignore hybrid reality. Running OpenTofu for new workloads while maintaining Terraform for legacy environments is often the most pragmatic approach. The risk comes not from using both tools, but from managing them inconsistently.</p><h3>Infrastructure as Code Licensing Is Now a Platform Concern</h3><p>The Terraform license change highlighted a broader shift: <a href="https://www.harness.io/products/infrastructure-as-code-management?tag=true">infrastructure as code</a> licensing is no longer a background concern. It is part of your platform architecture.</p><p>Infrastructure teams now have to think about:</p><ul><li>How licensing choices affect downstream products and services.</li><li>Whether tooling decisions introduce future renegotiation risk.</li><li>How easily execution engines can be swapped if assumptions change again?</li></ul><p>This is where open source IaC tools regain their appeal. Not because they are “better,” but because they reduce surprise.</p><h3>How Harness IaCM Supports OpenTofu and Terraform Without Lock-In</h3><p>Regardless of where you land in the OpenTofu vs Terraform discussion, the operational challenge remains the same: running infrastructure changes safely, consistently, and with governance across teams.</p><p>Harness <a href="https://www.harness.io/products/infrastructure-as-code-management?tag=true">Infrastructure as Code Management</a> (IaCM) is designed to absorb this kind of tooling evolution. It supports both OpenTofu and Terraform while treating the execution engine as an implementation detail rather than a strategic constraint.</p><p>With Harness IaCM:</p><p>This approach allows teams to adopt OpenTofu where openness and governance matter most, while continuing to support Terraform where existing investment justifies it. <a href="https://developer.harness.io/docs/infra-as-code-management/pipelines/operations/drift-detection">Drift detection</a> and <a href="https://developer.harness.io/docs/infra-as-code-management/policies-governance/terraform-plan-cost-policy">policy enforcement</a> apply uniformly, regardless of which tool is executing the plan.</p><p>Rather than forcing an immediate migration, Harness IaCM enables controlled evaluation and gradual transition, reducing the risk that licensing decisions fragment your platform.</p><h3>Summary: Make Licensing a Design Decision, Not a Surprise</h3><p>The Terraform license change was a reminder that infrastructure tooling choices have consequences beyond syntax and state files. The BSL vs open source distinction affects governance, risk tolerance, and long-term platform flexibility.</p><p>OpenTofu provides a clear open baseline for teams who want to avoid licensing ambiguity going forward. Terraform remains relevant, especially where commercial tooling is already embedded. The key is designing your platform so that this choice does not become a single point of failure.</p><p>Harness Infrastructure as Code Management helps teams do exactly that. By supporting both OpenTofu and Terraform with consistent workflows and guardrails, it allows organizations to adapt without disruption.</p><p>The question is no longer which tool you prefer. It is whether your infrastructure platform can handle change when the rules shift again.</p><p>Explore <a href="https://developer.harness.io/docs/infrastructure-as-code-management">Harness IaCM</a> and design your IaC workflows for flexibility, not assumptions.</p><p>Check out the <a href="https://opentofu.org/docs/intro/migration/">Terraform to OpenTofu migration</a> to bring this flexibility into play and take back control of your infrastructure.</p><p><em>Originally published at </em><a href="https://www.harness.io/blog/terraform-license-change-bsl-vs-open-source-guide"><em>https://www.harness.io</em></a><em> on March 31, 2026.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1cf4b397c632" width="1" height="1" alt=""><hr><p><a href="https://medium.com/harness-engineering/terraform-license-change-bsl-vs-open-source-guide-1cf4b397c632">Terraform License Change: BSL vs Open Source Guide</a> was originally published in <a href="https://medium.com/harness-engineering">Harness Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Load Testing: An Essential Guide for 2026]]></title>
            <link>https://medium.com/harness-engineering/load-testing-an-essential-guide-for-2026-614aa8285434?source=rss----c7a8b2228e23---4</link>
            <guid isPermaLink="false">https://medium.com/p/614aa8285434</guid>
            <dc:creator><![CDATA[Harness Engineering]]></dc:creator>
            <pubDate>Fri, 03 Apr 2026 04:52:33 GMT</pubDate>
            <atom:updated>2026-04-03T04:52:34.920Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*CJYKjrbO1WV22QMQ.png" /></figure><p>Author: <a href="https://www.harness.io/authors/pritesh-kiri">Pritesh Kiri</a></p><p>In today’s always-on digital economy, a single slow page or unexpected crash during peak traffic can cost businesses thousands or even millions of dollars in lost revenue, damaged reputation, and frustrated customers. Imagine Black Friday shoppers abandoning carts because your e-commerce site buckles under load, or a SaaS platform going down during a major product launch. This is where <a href="https://developer.harness.io/docs/resilience-testing/load-testing/get-started">load testing</a> becomes non-negotiable.</p><p>Load testing simulates real-world user traffic to ensure your applications, websites, and APIs stay fast, stable, and scalable. It’s a cornerstone of performance testing that helps teams catch bottlenecks early, validate SLAs, and build resilient systems.</p><p>If you’re searching for a complete load testing guide, what is load testing, or how to perform load testing, you’re in the right place. This beginner-friendly introduction covers everything from the basics to best practices, with practical steps anyone can follow.</p><h3>What Is Load Testing?</h3><p>Load testing is a type of performance testing that evaluates how your system behaves under expected (and sometimes peak) user loads. It simulates concurrent users, requests, or transactions to measure key metrics such as Response times (average, p95, p99), Throughput (requests per second), Error rates, Resource utilization (CPU, memory, database connections), Latency and scalability.</p><p>Unlike unit or functional tests that check “does it work?”, load testing answers: “How does it perform when 1,000 (or 100,000) people use it at once?”</p><p>Done early and often, load testing reduces risk across the lifecycle. It confirms capacity assumptions, reveals infrastructure limits, and proves that recent changes haven’t slowed critical paths. The result is fewer production incidents and fewer late-night fire drills.</p><p>Key terminology to anchor your approach:</p><ul><li>Response time: End-to-end time to complete a request.</li><li>Latency: The network delay portion of response time.</li><li>Throughput: Requests or transactions per second.</li><li>Concurrency: The number of simultaneous users or sessions.</li><li>Virtual users: Emulated users that generate traffic.</li><li>Think time: Pauses between actions to mimic real behavior.</li><li>Error rate: Percentage of requests that fail.</li><li>Saturation point: Load level where performance drops sharply.</li><li><a href="https://www.harness.io/harness-devops-academy/what-is-a-service-level-objective-slo">Service-level objective</a> (SLO): A target like p95 response time under 500 ms.</li></ul><h3>Why Load Testing Matters?</h3><p>Effective load testing quantifies capacity, validates autoscaling, and uncovers issues like thread pool starvation, database contention, cache thrash, and third-party limits. With data in hand, you can tune connection pools, garbage collection, caching tiers, and CDN strategies so the app stays fast when it counts.</p><p>Skipping load testing is like launching a rocket without wind-tunnel tests, risky and expensive. Here’s why it’s essential:</p><ul><li>Prevents costly downtime: Unplanned outages average $5,600-$14,000+ per minute for enterprises, with some large companies facing $1M+ per hour.</li><li>Improves user experience and conversions: Even 100ms of added latency can reduce sales by 1% and one-second delays can drop conversions by 7% for businesses.</li><li>Validates scalability and auto-scaling: Especially critical for cloud-native apps on Kubernetes or AWS.</li><li>Saves money long-term: Catch issues in staging instead of production; reduce infrastructure over-provisioning.</li><li>Boosts confidence for high-traffic events: Product launches, sales, seasonal peaks.</li></ul><p>Investing in load testing upfront keeps teams focused on building, not firefighting. Many major outages (think major retailers or banking apps) trace back to untested load scenarios. Load testing helps you ship with confidence.</p><h3>Types of Load Testing</h3><p>Not all traffic patterns are the same, and your system shouldn’t be tested with a one-size-fits-all approach. Different load testing scenarios help you understand how your application behaves under various real-world conditions, from everyday usage to extreme, unpredictable events.</p><ul><li><strong>Baseline testing</strong> — This scenario tests your system under normal, expected load conditions. It helps establish a performance benchmark so you can compare how the system behaves under higher stress levels later.</li><li><strong>Ramp-up / Steady-state testing</strong> — In this approach, the load is gradually increased until it reaches a peak and is then maintained for a period of time. This helps you observe how your system handles growth in traffic and whether it can sustain peak load consistently.</li><li><strong>Stress testing</strong> — In this type of testing you push a system beyond its limits to see what happens when it starts to fail. In simple words, you keep increasing users or traffic until the system can’t handle it anymore.</li><li><strong>Spike testing</strong> — Spike testing simulates sudden and extreme increases in traffic over a short period. It is useful for understanding how your system reacts to unexpected surges, such as flash sales or viral events.</li><li><strong>Soak / Endurance testing</strong> — This type of testing runs the system under a steady load for an extended duration. It helps uncover issues like memory leaks, resource exhaustion, or performance degradation over time.</li></ul><h3>How to Perform Load Testing?</h3><ol><li><strong>Define objectives and scope</strong> — Start by clearly defining what success looks like for your system. <a href="https://www.harness.io/harness-devops-academy/what-is-a-service-level-agreement-sla">Identify your SLAs</a>, expected concurrent users, peak traffic events, and the most critical user journeys like login, checkout, or key API calls.</li><li><strong>Identify test scenarios</strong> -Use real production data such as analytics and logs to design realistic test scenarios. Make sure to include both common user flows (happy paths) and edge cases that could potentially break the system.</li><li><strong>Set up test environment</strong>- Create a test environment that closely mirrors production in terms of infrastructure, data volume, and network conditions.</li><li><strong>Create scripts</strong> — Develop scripts that simulate real user behavior using load testing tools. Incorporate think times, random delays, and varied actions to mimic how actual users interact with your system.</li><li><strong>Configure load profile</strong> — Define how the load will be applied, gradually ramp up users, maintain a steady load, and then ramp down. This helps you observe how the system behaves under increasing and sustained pressure.</li><li><strong>Execute the test</strong> — Run your tests in a controlled, non-production environment first to avoid impacting real users. Monitor system performance in real-time to catch any immediate issues.</li><li><strong>Analyze results</strong> — Dive into the results using logs, dashboards, and APM tools to identify performance bottlenecks. Focus on metrics like response time, error rates, and system throughput.</li><li><strong>Iterate and report</strong> — Fix the issues identified during testing and re-run tests to validate improvements. Document your findings and share insights with stakeholders for better decision-making.</li><li><strong>Integrate into CI/CD pipelines</strong> — Automate load testing by integrating it into your CI/CD pipelines. This ensures performance is continuously validated with every build or deployment.</li></ol><p>Load testing isn’t just about throwing traffic at your system, it’s about understanding how your application behaves under real-world conditions and uncovering hidden bottlenecks before your users do.</p><p>Here’s a step-by-step guide to do load testing:</p><p>Load testing is an iterative process, not a one-time activity. The more consistently you test and refine, the more resilient and reliable your system becomes over time.</p><h3>Role of AI in load testing</h3><p>Moving into 2026 and beyond, AI is shifting load testing from a manual, scheduled chore into an intelligent, autonomous process. Instead of relying on static scripts, AI agents now ingest vast streams of real-world data including <strong>recent incident reports, deployment logs, and even design changes documented in wikis</strong> to generate context-sensitive testing scenarios. This ensures that performance suites are no longer generic; they are hyper-targeted to the specific risks introduced by the latest code commits or environmental shifts, allowing teams to catch bottlenecks before they ever reach production.</p><p>The relationship between testing and infrastructure has also become a two-way street. Beyond just identifying breaking points, AI-driven analysis of load test results now provides <strong>proactive recommendations for deployment configurations</strong>. By correlating performance metrics with resource allocation, these systems can suggest the “golden path” for auto-scaling thresholds, memory limits, and container orchestration. This creates a continuous feedback loop where the load test doesn’t just pass or fail it actively optimizes the production environment for peak efficiency.</p><h3>Load testing for AI Agents</h3><p>In the new landscape of AI agents proliferation, load testing is no longer just about hitting a server with traffic it’s about managing the explosion of <a href="https://www.harness.io/blog/agentic-ai-in-devops-the-architects-guide-to-autonomous-infrastructure"><strong>agentic orchestration</strong></a>. With organizations deploying hundreds of specialized AI agents, a single user request can trigger a “storm” of inter-agent communication, where one agent’s output becomes another’s prompt. Traditional load tests fail here because they can’t predict these <strong>emergent behaviors</strong> or the cascading latency that occurs when multiple agents reason, call external APIs, and update shared memory simultaneously. Testing must now account for <strong>“prompt bloat”</strong> and context contamination, where excessive or conflicting data fed into these agent chains causes performance to degrade or costs to spike unexpectedly.</p><p>To survive this complexity, performance engineering in 2026 has shifted toward <strong>dynamic environment testing</strong> and automated “prompt volume” estimation. Load testers are now using tools like <strong>AI Gateways</strong> to monitor and rate-limit the massive volume of prompts moving between agents, ensuring that “reasoning loops” don’t turn into infinite, resource-draining cycles. By simulating thousands of parallel agent trajectories in <strong>virtual sandboxes</strong>, teams can identify the specific point where a flurry of prompts causes an LLM’s context window to “clash,” leading to the 30–40% drops in accuracy often seen under heavy organizational load.</p><h3>Popular Load Testing Tools</h3><ul><li>Open-source/free: Apache JMeter, k6 (Grafana), Gatling, Locust (Python-based).</li><li>Enterprise: Harness, LoadRunner, NeoLoad, BlazeMeter, LoadNinja.</li></ul><p>When selecting a load testing tool, teams often start with open-source options for flexibility and cost, then move to enterprise or cloud-managed solutions for scale, collaboration, and integrations.</p><p>Here are some of the most popular and widely used load testing tools in 2026:</p><p>Choose based on scripting language, scale needs, and integration. For teams already invested in Locust or seeking to combine load testing with chaos engineering in CI/CD pipelines, platforms like <a href="https://developer.harness.io/docs/resilience-testing">Harness Resilience Testing</a> provide seamless native support to elevate your testing strategy.</p><h3>Load Testing Best Practices in 2026</h3><p>As systems grow more distributed and user expectations continue to rise, load testing in 2026 is no longer optional, it’s a continuous discipline. Following the right best practices ensures that your application is not just fast, but also resilient and reliable under real-world conditions.</p><p>Adopting these best practices helps you move beyond basic performance testing toward building truly resilient systems. In 2026, it’s not just about handling traffic, it’s about thriving under pressure.</p><h3>Conclusion</h3><p>Load testing turns unknowns into knowns and panic into process. It isn’t a “nice-to-have”, it’s essential for delivering fast, reliable digital experiences that customers (and your bottom line) demand.</p><p>By following this guide, you’ll identify issues early, optimize performance, and build systems that scale confidently.</p><p>Ship faster, break less, and stay resilient.</p><p><em>Originally published at </em><a href="https://www.harness.io/blog/load-testing-an-essential-guide-for-2026"><em>https://www.harness.io</em></a><em> on March 31, 2026.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=614aa8285434" width="1" height="1" alt=""><hr><p><a href="https://medium.com/harness-engineering/load-testing-an-essential-guide-for-2026-614aa8285434">Load Testing: An Essential Guide for 2026</a> was originally published in <a href="https://medium.com/harness-engineering">Harness Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Branch-Scoped Sequence IDs in Harness CI]]></title>
            <link>https://medium.com/harness-engineering/branch-scoped-sequence-ids-in-harness-ci-46281eeda9f5?source=rss----c7a8b2228e23---4</link>
            <guid isPermaLink="false">https://medium.com/p/46281eeda9f5</guid>
            <dc:creator><![CDATA[Harness Engineering]]></dc:creator>
            <pubDate>Wed, 01 Apr 2026 06:58:54 GMT</pubDate>
            <atom:updated>2026-04-01T06:58:55.448Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*PZQadEpoWjEqqHuX.png" /></figure><p>Authors: <a href="https://www.harness.io/authors/abhay-ganvir">Abhay Ganvir</a></p><p>You’re tagging Docker images with build numbers.</p><p>-Build #47 is your latest production release on main. A developer pushes a hotfix to release-v2.1, that run becomes build #48.</p><p>-Another merges to develop, build #49. A week later someone asks: “What build number are we on for production?” You check the registry.</p><p>-You see #47, #52, #58, #61 on main. The numbers in between? Scattered across feature branches that may never ship. Your build numbers have stopped telling a useful story.</p><p>That’s the reality when your CI platform uses a single global counter. Every run, on every branch, increments the same number. For teams using GitFlow, trunk-based development, or any branching strategy, that means gaps, confusion, and versioning that doesn’t match how you actually ship.</p><p>TL;DR: Harness CI now supports branch-scoped build sequence IDs via &lt;+pipeline.branchSeqId&gt;.</p><p>Each branch gets its own counter. No gaps. No confusion.</p><h3>Why Global Build Counters Break Down</h3><p>Most CI platforms give you one incrementing counter per pipeline. Push to main, push to develop, push to a feature branch, same counter. So you get:</p><ul><li>Gaps in the sequence for any given branch (e.g. main might have #1, #4, #7).</li><li>No clear answer to “what’s the latest build on main?”</li><li>Semantic versioning and artifact naming that don’t line up with branch reality.</li><li>Registries and artifact stores full of numbers that don’t map to how you release.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*p55xQVWRwJZV6wWr.png" /></figure><p>This is now built directly into Harness CI as a first-class capability.</p><h3>What It Feels Like in Practice</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/640/0*6yH-6BAzJcKSvBRt.png" /></figure><p>Add &lt;+pipeline.branchSeqId&gt; where you need the number-for example, in a Docker build-and-push step:</p><p>Trigger runs on main, then on develop, then on a feature branch. Each branch gets its own sequence: main might be 1, 2, 3… develop 1, 2, 3… feature/x 1, 2. Your tags become meaningful: main-42, develop-15, feature-auth-3. No more guessing which number belongs to which branch.</p><ul><li>Per-branch counters — One sequence per pipeline + repo + branch, stored and incremented atomically.</li><li>Pipeline expression — &lt;+pipeline.branchSeqId&gt;. Check out <a href="https://developer.harness.io/docs/platform/variables-and-expressions/add-a-variable/#reference-variables">Harness variables documentation.</a></li><li>REST API — List sequences for a pipeline, get the current value for a branch/repo, reset a branch counter, or set it to a specific value (for example, after a major release or when migrating from another CI).</li><li>Consistent identification — Repo URLs and branch names are normalized (for example, refs/heads/main → main, different URL forms to one canonical host/owner/repo). Same logical branch and repo always share the same counter.</li><li>Cleanup — When a pipeline is deleted, its branch-sequence data is removed so you don’t leave orphaned counters.</li></ul><h3>What You Get</h3><p>Webhook triggers (push, PR, branch, release) and manual runs (with branch from codebase config) are supported. For tag-only or other runs without branch context, the expression returns null so you can handle that in your pipeline if needed.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*K7nhPs6xQZl--uSU.png" /></figure><h3>How It Works Under the Hood</h3><p>Branch and repo are taken from the trigger payload when possible (webhooks) or from the pipeline’s codebase configuration (for example, manual runs). We normalize them so that the same repo and branch always map to the same logical key: branch names get refs/heads/ (or similar) stripped, and repo URLs are reduced to a canonical form (for example, github.com/org/repo). That way, whether you use <a href="https://...,/">https://...,</a> git@…, or different casing, you get one counter per branch.</p><p>The counter is stored and updated with an atomic increment. Parallel runs on the same branch still get distinct, sequential numbers. The value is attached to the run’s metadata and exposed through the pipeline execution context so &lt;+pipeline.branchSeqId&gt; resolves correctly at runtime.</p><ul><li>Docker image tagging: use &lt;+pipeline.branchSeqId&gt; and optionally &lt;+codebase.branch&gt;-&lt;+pipeline.branchSeqId&gt; for clear, branch-specific tags.</li><li>Helm chart versioning: e.g. — version 1.0.&lt;+pipeline.branchSeqId&gt; --app-version &lt;+codebase.commitSha&gt; so the chart version tracks the build number and the app version tracks the commit.</li><li>Release notes or deployment labels: for example, “Release Build #&lt;+pipeline.branchSeqId&gt;&quot; so production and staging each have a clear, branch-local build number.</li></ul><h3>Putting It to Work</h3><p>For teams that need control or migration support, branch sequences are also manageable via API:</p><pre># List all branch sequences for a pipelineGET /pipelines/{pipelineIdentifier}/branch-sequences # Reset counter for a specific branchDELETE /pipelines/{pipelineIdentifier}/branch-sequences/branch?branch=main&amp;repoUrl=github.com/org/repo # Set counter to a specific value (e.g., after major release)PUT /pipelines/{pipelineIdentifier}/branch-sequences/set?branch=main&amp;repoUrl=github.com/org/repo&amp;sequenceId=100</pre><p>All of this is gated by the same feature flag so only accounts that have adopted the feature use the APIs.</p><ol><li>Enable the feature — Turn on CI_ENABLE_BRANCH_SEQUENCE_ID (Account Settings → Feature Flags, or Reach out to the <a href="https://www.harness.io/company/contact-sales">Harness team</a>).</li><li>Use the expression — Add &lt;+pipeline.branchSeqId&gt; in steps, tags, or env vars.</li><li>Verify — Run the pipeline on two or three branches and confirm each branch has its own 1, 2, 3…</li></ol><h3>Try It (With Smart Guardrails)</h3><p>If branch context isn’t available, the expression returns null. Design your pipeline to handle that (for example, skip tagging or use a fallback) for tag builds or edge cases.</p><p>Feature availability may vary by plan. Check with your Harness account or <a href="https://developer.harness.io/">Harness Developer Hub</a> for your setup.</p><h3>How Other CI Platforms Handle This (Spoiler: Most Don’t)</h3><p>This isn’t just a Harness problem we solved-it’s an industry gap. Here’s how major CI platforms compare:</p><p>Most platforms treat build numbers as an afterthought. Harness CI treats them as a first-class versioning primitive. For teams migrating from Jenkins or Azure DevOps, the model will feel familiar. For teams on GitHub Actions, GitLab, or CircleCI, this fills a gap that previously required external services or custom scripts</p><h3>What’s Coming</h3><p>This is the first release of branch-scoped sequence IDs. The foundations are in place: per-branch counters, expression support, and APIs. We’re not done.</p><p>We’re listening. If you use this feature and hit rough edges-or have ideas for tag-scoped sequences, dashboard visibility, or trigger conditions-we want to hear about it. <a href="https://community.harness.io/">Share feedback</a> .</p><p><em>Originally published at </em><a href="https://www.harness.io/blog/build-numbers-that-actually-make-sense-branch-scoped-sequence-ids-in-harness-ci"><em>https://www.harness.io</em></a><em> on March 31, 2026.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=46281eeda9f5" width="1" height="1" alt=""><hr><p><a href="https://medium.com/harness-engineering/branch-scoped-sequence-ids-in-harness-ci-46281eeda9f5">Branch-Scoped Sequence IDs in Harness CI</a> was originally published in <a href="https://medium.com/harness-engineering">Harness Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Agent-Native Repo: Why AGENTS.MD is the New Standard]]></title>
            <link>https://medium.com/harness-engineering/the-agent-native-repo-why-agents-md-is-the-new-standard-1f1872c628df?source=rss----c7a8b2228e23---4</link>
            <guid isPermaLink="false">https://medium.com/p/1f1872c628df</guid>
            <dc:creator><![CDATA[Harness Engineering]]></dc:creator>
            <pubDate>Wed, 01 Apr 2026 06:43:09 GMT</pubDate>
            <atom:updated>2026-04-01T06:43:10.795Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*S2a9O3jaX-LFdtzN.png" /></figure><p>Author: <a href="https://www.harness.io/authors/dewan-ahmed">Dewan Ahmed</a>, <a href="https://www.harness.io/authors/shreyas-nagaraj">Shreyas Nagaraj</a></p><p>This is part 1 of a five-part series on building production-grade AI engineering systems.</p><p>Across this series, we will cover:</p><ol><li>How to make your repository agent-native</li><li>How to prevent context decay in long AI sessions</li><li>How to orchestrate tools, subagents, and external systems</li><li>How to survive the multi-model reality with gateway layers</li><li>How to measure and enforce quality with AI evals</li></ol><p>Most teams experimenting with AI coding agents focus on prompts.</p><p>That is the wrong starting point.</p><p>Before you optimize how an agent thinks, you must standardize what it sees.</p><p>AI agents do not primarily fail because of reasoning limits. They fail because of environmental ambiguity. They are dropped into repositories designed exclusively for humans and expected to infer structure, conventions, workflows, and constraints from scattered documentation.</p><p>If <a href="https://www.harness.io/products/ai-native-software-delivery/ai-code-agent">AI agents</a> are contributors, then the repository itself must become agent-native.</p><p>The foundational step is introducing a standardized instruction layer that every agent can read.</p><p>That layer is AGENTS.md.</p><p><strong>The Real Problem: Context Silos</strong></p><p>Every coding agent needs instructions. Where those instructions live depends on the tool.</p><p>One IDE reads from a hidden rules directory.<br>Another expects a specific markdown file.<br>Another uses proprietary configuration.</p><p>This fragmentation creates three systemic problems.</p><p><strong>1. Tool-dependent prompt locations</strong></p><p>Instructions are locked into IDE-specific paths. Change tools and you lose institutional knowledge.</p><p><strong>2. Tribal knowledge never gets committed</strong></p><p>When a developer discovers the <a href="https://www.harness.io/blog/agentic-ai-in-devops-the-architects-guide-to-autonomous-infrastructure">right way to guide an agent</a> through a complex module, that guidance often lives in chat history. It never reaches version control. It never becomes part of the repository’s operational contract.</p><p><strong>3. Inconsistent agent behavior</strong></p><p>Two engineers working on the same codebase but using different agents receive different outputs because the instruction surfaces are different.</p><p>The repository stops being the single source of truth.</p><p>For human collaboration, we solved this decades ago with READMEs, contribution guides, and ownership files. For AI collaboration, we are only beginning to standardize.</p><p><strong>What AGENTS.md Is</strong></p><p><a href="http://agents.md/">AGENTS.md</a> is a simple, open, tool-agnostic format for providing coding agents with project-specific instructions. It is now part of the broader open agentic ecosystem under the Agentic AI Foundation, with broad industry adoption.</p><p>It is not a replacement for README.md. It is a complement.</p><p>Design principle:</p><ul><li>README.md is for humans.</li><li>AGENTS.md is for agents.</li></ul><p>Humans need quick starts, architecture summaries, and contribution policies.</p><p>Agents need deterministic build commands, exact test execution steps, linter requirements, directory boundaries, prohibited patterns, and explicit assumptions.</p><p>Separating these concerns provides:</p><p>Several major open source repositories have already adopted AGENTS.md. The pattern is spreading because it addresses a real structural gap.</p><p>Recent evaluations have also shown that explicit repository-level agent instructions outperform loosely defined “skills” systems in practical coding scenarios. The implication is clear. Context must be explicit, not implied.</p><p><strong>A Real Example: OpenAI’s Agents SDK</strong></p><p>A practical example of this pattern can be seen in the OpenAI Agents Python SDK repository.</p><p>The project contains a root-level AGENTS.md file that defines operational instructions for contributors and AI agents working on the codebase. You can view the full file <a href="https://github.com/openai/openai-agents-python/blob/main/AGENTS.md">here: Github</a>.</p><p>Instead of leaving workflows implicit, the repository encodes them directly into agent-readable instructions. For example, the file requires contributors to run verification checks before completing changes:</p><pre>Run `$code-change-verification` before marking work complete.</pre><p>It also explicitly scopes where those rules apply, such as changes to core source code, tests, examples, or documentation within the repository.</p><p>Rather than expecting an agent to infer these processes from scattered documentation, the project defines them as explicit instructions inside the repository itself.</p><p>This is the core idea behind AGENTS.md.</p><p>Operational guidance that would normally live in prompts, chat history, or internal knowledge becomes version-controlled infrastructure.</p><p><strong>Designing an Effective Root AGENTS.md</strong></p><p>A root AGENTS.md should be concise. Under 300 lines is a good constraint. It should be structured, imperative, and operational.</p><p>A practical structure includes four required sections.</p><h3>1. Project Overview</h3><p>This section establishes the mental model.</p><p>Include:</p><ul><li>Project purpose and high-level architecture</li><li>Directory structure and key components</li><li>Technology stack and critical dependencies</li></ul><p>Agents are pattern matchers. The clearer the structural map, the fewer incorrect assumptions they make.</p><h3>2. Build, Test, and Push Instructions</h3><p>This section must be precise.</p><p>Include:</p><p>Avoid vague language. Replace “run tests” with explicit commands.</p><p>Agents execute what they are told. Precision reduces drift.</p><h3>3. Development Workflow</h3><p>This section defines conventions.</p><p>Rather than bloating AGENTS.md, reference a separate coding standards document for:</p><p>The root file should stay focused while linking to deeper guidance.</p><h3>4. Common Pitfalls and Prohibited Patterns</h3><p>This is where most teams underinvest.</p><p>Document:</p><p>Agents tend to repeat statistically common patterns. Your codebase may intentionally diverge from those patterns. This section is where you enforce that divergence.</p><p>Think of this as defensive programming for AI collaboration.</p><p><strong>Hierarchical AGENTS.md: Scaling Context Correctly</strong></p><p>Large repositories require scoped context.</p><p>A single root file cannot encode all module-specific constraints without becoming noisy. The solution is hierarchical AGENTS.md files.</p><p>Structure example:</p><pre>root/ AGENTS.md module-a/ AGENTS.md module-b/ AGENTS.md sub-feature/ AGENTS.md</pre><p>Agents automatically read nested AGENTS.md files when operating inside those directories. Context scales from general to specific.</p><p>Root defines global conventions.<br>Module-level files define local invariants.<br>Feature-level files encode edge-case constraints.</p><p>This reduces irrelevant context and increases precision.</p><p>It also mirrors how humans reason about codebases.</p><p><strong>Compatibility Across Tools</strong></p><p>A standard file location matters.</p><p>Some agents natively read AGENTS.md. Others require simple compatibility mechanisms such as symlinks that mirror AGENTS.md into tool-specific filenames.</p><p>The key idea is a single source of truth.</p><p>Do not maintain multiple divergent instruction files. Normalize on AGENTS.md and bridge outward if needed.</p><p>The goal is repository-level portability. Change tools without losing institutional knowledge.</p><p><strong>Best Practices for Agent Instructions</strong></p><p>To make AGENTS.md effective, follow these constraints.</p><p><strong>Write imperatively.<br></strong> Use direct commands. Avoid narrative descriptions.</p><p><strong>Avoid redundancy.<br></strong> Do not duplicate README content. Reference it.</p><p><strong>Keep it operational.<br></strong> Focus on what the agent must do, not why the project exists.</p><p><strong>Update it as the code evolves.<br></strong> If the build process changes, AGENTS.md must change.</p><p><strong>Treat violations as signal.<br></strong> If agents consistently ignore documented rules, either the instruction is unclear or the file is too long and context is being truncated. Reset sessions and re-anchor.</p><p>AGENTS.md is not static documentation. It is part of the execution surface.</p><p><strong>Ownership and Governance</strong></p><p>If agents are contributors, then their instruction layer requires ownership.</p><p>Each module-level AGENTS.md should be maintained by the same engineers responsible for that module. Changes to these files should follow the same review rigor as code changes.</p><p>Instruction drift is as dangerous as code drift.</p><p>Version-controlled agent guidance becomes part of your engineering contract.</p><p><strong>Why Teams Are Adopting AGENTS.md</strong></p><p>Repositories across the industry have begun implementing AGENTS.md as a first-class artifact. Large infrastructure projects, developer tools, SDKs, and platform teams are standardizing on this pattern.</p><p>The motivation is consistent:</p><p>AGENTS.md transforms prompt engineering from a personal habit into a shared, reviewable, versioned discipline.</p><p>Vercel <a href="https://vercel.com/blog/agents-md-outperforms-skills-in-our-agent-evals">published evaluation results</a> showing that repository-level AGENTS.md context outperformed tool-specific skills in agent benchmarks.</p><p><strong>Why This Matters Now</strong></p><p>AI agents are rapidly becoming embedded in daily development workflows.</p><p>Without a standardized instruction layer:</p><p>The repository must become the stable contract between humans and machines.</p><p>AGENTS.md is the first structural step toward that contract.</p><p>It shifts agent collaboration from ad hoc prompting to engineered context.</p><p><strong>Foundation Before Optimization</strong></p><p>In the next post, we will examine a different failure mode.</p><p>Even with a perfectly structured AGENTS.md, long AI sessions degrade. Context accumulates. Signal dilutes. Hallucinations increase. Performance drops as token counts rise.</p><p>This phenomenon is often invisible until it causes subtle architectural damage.</p><p>Part 2 will focus on defeating context rot and enforcing session discipline using structured planning, checkpoints, and meta-prompting.</p><p>Before you scale orchestration.<br>Before you add subagents.<br>Before you optimize cost across multiple model providers.</p><p>You must first stabilize the environment.</p><p>An agent-native repository is the foundation.</p><p>Everything else builds on top of it.</p><p><em>Originally published at </em><a href="https://www.harness.io/blog/the-agent-native-repo-why-agents-md-is-the-new-standard"><em>https://www.harness.io</em></a><em> on March 19, 2026.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1f1872c628df" width="1" height="1" alt=""><hr><p><a href="https://medium.com/harness-engineering/the-agent-native-repo-why-agents-md-is-the-new-standard-1f1872c628df">The Agent-Native Repo: Why AGENTS.MD is the New Standard</a> was originally published in <a href="https://medium.com/harness-engineering">Harness Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Architecting MCP for AI Agents: Lessons from Our Redesign]]></title>
            <link>https://medium.com/harness-engineering/architecting-mcp-for-ai-agents-lessons-from-our-redesign-420e0713e84f?source=rss----c7a8b2228e23---4</link>
            <guid isPermaLink="false">https://medium.com/p/420e0713e84f</guid>
            <dc:creator><![CDATA[Harness Engineering]]></dc:creator>
            <pubDate>Fri, 20 Mar 2026 04:47:16 GMT</pubDate>
            <atom:updated>2026-03-20T04:47:16.695Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*45DyjBZB3mrBSXH5.png" /></figure><p>Author: <a href="https://www.harness.io/authors/sunil-gattupalle">Sunil Gattupalle</a>, <a href="https://www.harness.io/authors/rohan-gupta">Rohan Gupta,</a> <a href="https://www.harness.io/authors/sj">Shubham Jindal</a></p><p>—</p><p>Key Takeaways:</p><p><em>The </em><strong><em>Harness MCP server</em></strong><em> is an MCP-compatible interface that lets AI agents discover, query, and act on Harness resources across </em><a href="https://www.harness.io/products/continuous-integration"><em>CI</em></a><em>/</em><a href="https://www.harness.io/products/continuous-delivery"><em>CD</em></a><em>, </em><a href="https://www.harness.io/products/continuous-delivery/harness-gitops"><em>GitOps</em></a><em>, </em><a href="https://www.harness.io/products/feature-management-experimentation"><em>Feature Flags</em></a><em>, </em><a href="https://www.harness.io/products/cloud-cost-management"><em>Cloud Cost Management</em></a><em>, </em><a href="https://www.harness.io/products/application-security-testing"><em>Security Testing</em></a><em>, </em><a href="https://www.harness.io/products/resilience-testing"><em>Resilience Testing</em></a><em>, </em><a href="https://www.harness.io/products/internal-developer-portal"><em>Internal Developer Portal</em></a><em>, and more.</em></p><ul><li>The Harness MCP server v2 reduces tools from 130+ to 11.</li><li>The redesign cuts estimated tool-definition context cost from about 26% to 1.6% of a 200K-token window.</li><li>A registry-based dispatch model supports 125+ resource types without expanding the tool vocabulary.</li><li>The architecture is designed for Cursor, Claude Code, and other MCP-compatible clients.</li><li>Built-in safety controls include confirmation for writes, fail-closed deletes, and read-only mode.</li></ul><p>—</p><p>The first wave of MCP servers followed a natural pattern: take every API endpoint, wrap it in a tool definition, and expose it to the LLM. It was fast to build, easy to reason about, and it was exactly how we built the <a href="https://github.com/harness/mcp-server">first Harness MCP server</a>. That server taught us a lot: solid Go codebase, well-crafted tools, broad platform coverage across 30 toolsets. It also taught us where the one-tool-per-endpoint model hits a wall.</p><p>For platforms the size of Harness, spanning the entire SDLC, the pattern doesn’t scale. When you expose one tool per API endpoint, you’re asking the LLM to be a routing layer, forcing it to do something a switch statement does better. Every tool definition consumes context that could be spent on reasoning. At ~175 tools, that’s ~26% of the LLM’s context window before the developer even types a prompt.</p><p>So we iterated. The Harness MCP v2 redesign does the same work with 11 tools at ~1.6% context consumption. <strong>The answer isn’t fewer features, it’s a different architecture</strong>: a registry-based dispatch model where the LLM reasons about <em>what</em> to do, and the server handles <em>how</em> to do it.</p><h3>What We Learned: Tool Sprawl and Agent Performance</h3><p>When an MCP client connects to a server, it loads every tool definition into the LLM’s context window. Every name, description, parameter schema, and annotation. For the first Harness server at ~130+ active tools, here’s what that costs:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Zc82_rFd5hS9nyTy.png" /></figure><p>v1 vs v2 comparison</p><p>That’s the core insight: <strong>the first server uses ~26% of context on tool definitions before any work begins.</strong> The v2 uses ~1.6%.</p><p>This isn’t a theoretical concern. Research on LLM behavior in large context windows, including Liu et al.’s “Lost in the Middle” findings, shows that models struggle to use information placed deep within long contexts. As <a href="https://www.spletzer.com/2026/03/shedding-dead-context/">Ryan Spletzer recently wrote</a>, dead context doesn’t sit inertly: “It dilutes the signal. The model’s attention is spread across everything in the window, so the more irrelevant context you pack in, the less weight the relevant context carries.”</p><p>Anthropic’s own engineering team has <a href="https://www.anthropic.com/engineering/code-execution-with-mcp">documented this trade-off</a>: direct tool calls consume context for each definition and result, and agents scale better when the tool surface area is deliberately constrained.</p><p>The problem compounds in real-world developer environments. If you’re running Cursor or Claude Code with a Playwright MCP, a GitHub MCP, and the Harness MCP, those tool definitions stack. <a href="https://www.youtube.com/watch?v=IMbgdtvJCnI">EclipseSource’s analysis</a> shows that a standard set of MCP servers can eat 20% of the context window before you even type a prompt. The recommendation: stay below 40% total context utilization. Any MCP server with 100+ tools, ours included, would consume more than half that budget on its own.</p><h3>How We Stack Up: Context Efficiency Across the MCP Ecosystem</h3><p>The context window tax isn’t unique to Harness: it’s an industry-wide problem. Here’s how the v2 server compares to popular MCP servers in the wild:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*1-i7oslP4i8OyWOf.png" /></figure><p>Comparison of popular MCP servers</p><p>‍<strong>Lunar.dev research: </strong>“5 MCP servers, 30 tools each → 150 total tools injected. Average tool description: 200–500 tokens. Total overhead: 30,000–60,000 tokens. Just in tool metadata.” MCP server v2 at ~3,150 tokens would represent just 5–10% of a typical multi-server setup’s overhead.</p><p><strong>Real-world Claude Code user: </strong>A developer on <a href="https://www.reddit.com/r/ClaudeCode">Reddit r/ClaudeCode</a> with Playwright, Context7, Azure, Postgres, Zen, and Firecrawl MCPs reported 83.3K tokens (41.6% of 200K) consumed by MCP tools immediately after /clear. That’s before a single prompt.</p><p><strong>Anthropic’s code execution findings: </strong>Anthropic’s engineering team <a href="https://www.anthropic.com/engineering/code-execution-with-mcp">reported</a> that a workflow consuming 150,000 tokens was reduced to ~2,000 tokens (a 98.7% reduction) by switching from direct tool calls to code-based tool invocation. The principle is clear: fewer, smarter tools beat more, narrower ones.</p><p><strong>MCPAgentBench: </strong>An <a href="https://arxiv.org/abs/2512.24565">academic benchmark</a> found that “nearly all evaluated models exhibit a decline of over 10 points in task efficiency when tool selection complexity increases.” Models overwhelmed with tools prioritize task resolution over execution efficiency. They get the job done, but waste tokens doing it.</p><h3>IDE Tool Limits and Practical Headroom</h3><p>Cursor enforces an <strong>80-tool cap</strong>, OpenAI limits to 128 tools, and Claude supports up to ~120. The v2 server’s 11 tools leave massive headroom to run Harness alongside other MCP servers without hitting these limits.</p><p>Consider a concrete example: a developer running Cursor with Playwright (21 tools), GitHub MCP (~40 tools), and the old Harness MCP (~175 tools) would hit <strong>~236 tools, well past Cursor’s 80-tool cap</strong>. With v2 Harness (11 tools), the same stack is <strong>72 tools, comfortably under the limit</strong>.</p><p>With Claude Code, the same old stack would burn ~76,400 tokens (~38%) on tool definitions alone. With v2, it drops to ~27,550 tokens (~14%), <strong>freeing ~48,850 tokens</strong> for actual reasoning and conversation.</p><h3>The CLI vs MCP Debate and Why It’s the Wrong Question</h3><p>The MCP ecosystem is in the middle of a reckoning. <a href="https://www.scalekit.com/blog/mcp-vs-cli-use">Scalekit ran 75 benchmark runs</a> comparing CLI and MCP for identical GitHub tasks on Claude Sonnet 4, and CLI won on every efficiency metric: 10–32x cheaper, 100% reliable vs MCP’s 72%. For a simple “what language is this repo?” query, CLI used 1,365 tokens. MCP used 44,026 — almost entirely from schema injection of 43 tool definitions the agent never touched.</p><p>The Playwright team shipped the same verdict in hardware. Their new CLI tool saves browser state to disk instead of flooding context. In <a href="https://betterstack.com/community/guides/ai/playwright-cli-vs-mcp-browser/">BetterStack’s benchmarks</a>, CLI used ~150 tokens per interaction vs MCP’s ~7,400+ of accumulated page state. <a href="https://circleci.com/blog/mcp-vs-cli/">CircleCI found</a> CLI completed browser tasks with 33% better token efficiency and a 77 vs 60 task completion score.</p><p>The CLI camp’s argument is real: schema bloat kills performance. But their diagnosis points at the wrong layer. <strong>The problem isn’t MCP. It’s naive MCP server design.</strong></p><h3>What CLI Gets Right</h3><p>CLI wins when the agent already knows the tool. gh, kubectl, terraform: these have extensive training data. The agent composes commands from memory, pays zero schema overhead, and gets terse, predictable output. <a href="https://www.scalekit.com/blog/mcp-vs-cli-use">Scalekit found</a> that adding an 800-token “skills document” to CLI reduced tool calls and latency by a third.</p><p>CLI also wins on composition. Piping grep into jq into xargs chains operations in a single tool call. An MCP agent doing the same work makes N round-trips through the LLM, each one burning context.</p><h3>What CLI Can’t Do</h3><p>But CLI’s advantages dissolve the moment you cross three boundaries:</p><h4>Discovery</h4><p>CLI works when the agent knows the command. For a platform like Harness, with 122+ resource types across CI/CD, GitOps, FinOps, security, chaos, and IDP, the agent can’t know the API surface from training data alone. MCP’s harness_describe tool lets the agent discover capabilities at runtime. CLI would require the agent to guess curl commands against undocumented APIs.</p><h4>Authorization</h4><p>As <a href="https://www.scalekit.com/blog/mcp-vs-cli-use">Scalekit themselves concluded</a>: “The question isn’t CLI or MCP. It’s who is your agent acting for?” CLI auth gives the agent ambient credentials: your token. For multi-tenant, multi-user environments (which is where Harness operates), MCP provides per-user OAuth, explicit tool boundaries, and structured audit trails.</p><h4>Safety</h4><p>CLI agents can run arbitrary shell commands. An MCP server constrains the agent to declared tools with typed inputs. The v2 server’s elicitation-based confirmation flows, fail-closed deletes, and read-only mode are protocol-level safety guarantees that CLI can’t replicate.</p><h3>The v2 Server Is Our Answer to This Debate</h3><p>The CLI vs MCP debate is really about schema bloat and naive tool design. The v2 Harness MCP server eliminates the arguments against MCP without losing the arguments for it:</p><p><strong>Schema bloat? </strong>11 tools at ~3,150 tokens. That’s less than a single CLI help output for a complex tool. Cursor’s 80-tool cap? We use 11. The 44,026-token GitHub MCP problem? We’re 14x leaner.</p><p><strong>Round-trip overhead? </strong>The registry-based dispatch means the agent makes one tool call to harness_diagnose and gets back a complete execution analysis — pipeline structure, stage/step breakdown, timing, logs, and root cause. A CLI agent would need to chain 4–5 API calls to assemble the same picture.</p><p><strong>Discovery? </strong>harness_describe is a zero-API-call local schema lookup. The agent discovers 125+ resource types without a single network request. CLI would require a man page the agent has never seen.</p><p><strong>Composition? </strong>Skills + prompt templates encode multi-step workflows (build-deploy-app, debug-pipeline-failure) as server-side orchestration. The agent reasons about what to do; the server handles how to chain it. Same efficiency as a CLI pipe, with protocol-level safety.</p><p>The real lesson from the benchmarks: MCP servers with 43+ tools and no architecture for context efficiency will lose to CLI on cost metrics. But <strong>a well-designed MCP server with 11 tools, a registry, and a skills layer outperforms both naive MCP and naive CLI</strong> — and provides authorization, safety, and discoverability that CLI architecturally cannot.</p><h3>The Redesign: 11 Tools, 125+ Resource Types, 1 Registry</h3><p>We stopped designing for API parity and started designing for <strong>agent usability</strong>.</p><p>The v2 server is built around a registry-based dispatch model. Instead of one tool per endpoint, we expose 11 intentionally generic verbs. The intelligence lives in the registry: a declarative data structure that maps resource types to API operations.</p><h3>The 11 Tools</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*7BZvGlP8LfeORE_4.png" /></figure><p>11 tools in the MCP v2 server</p><p>When an agent calls harness_list(resource_type=&quot;pipeline&quot;), the server looks up pipeline in the registry, resolves the API path, injects scope parameters (account, org, project), makes the HTTP call, extracts the relevant response data, and appends a deep link to the Harness UI. The agent never needs to know the underlying API structure.</p><h3>How the Registry Works</h3><p>Each registry entry is a declarative ResourceDefinition:</p><p>{</p><p>resourceType: &quot;pipeline&quot;,</p><p>displayName: &quot;Pipeline&quot;,</p><p>toolset: &quot;pipelines&quot;,</p><p>scope: &quot;project&quot;,</p><p>identifierFields: [&quot;pipeline_id&quot;],</p><p>operations: {</p><p>list: {</p><p>method: &quot;GET&quot;,</p><p>path: &quot;/pipeline/api/pipelines/list&quot;,</p><p>queryParams: { search_term, page, size },</p><p>responseExtractor: (raw) =&gt; raw.content</p><p>},</p><p>get: {</p><p>method: &quot;GET&quot;,</p><p>path: &quot;/pipeline/api/pipelines/{pipeline_id}&quot;,</p><p>responseExtractor: (raw) =&gt; raw.data</p><p>}</p><p>}</p><p>}</p><p>‍</p><p>Adding support for a new Harness module requires adding one declarative object to the registry. No new tool definitions. No changes to MCP tool schemas. The LLM’s tool vocabulary stays constant as the platform grows.</p><p>Today, the registry covers <strong>125+ resource types across 30 toolsets</strong>, spanning the full Harness platform:</p><ul><li>DevOps: Pipelines, Executions, Services, Environments, Infrastructure, Templates, Connectors, Secrets, Delegates</li><li>Code: Repositories, Branches, Commits, Pull Requests, Code Reviews</li><li>GitOps: Agents, Applications, Clusters, ApplicationSets, Repositories</li><li>Security: Security Issues, Exemptions, SBOMs, Compliance, Supply Chain, OPA Policies</li><li>Cloud Cost: Perspectives, Budgets, Recommendations, Anomalies, Commitments</li><li>Chaos: Experiments, Probes, Templates, Infrastructure, Load Tests</li><li>Feature Flags: Workspaces, Environments, Flags</li><li>IDP: Entities, Scorecards, Workflows, Tech Docs</li><li>SEI: DORA Metrics, Team Analytics, AI Usage, Business Alignment</li><li>Platform: Organizations, Projects, Users, Roles, Permissions, Settings, Audit Trail</li></ul><h3>Optimized for Cursor, Claude Code, and Real Developer Workflows</h3><p>The architecture wasn’t designed in a vacuum. We built it specifically for the environments developers actually use.</p><h3>Cursor and Windsurf: Stdio-First, Toolset Filtering</h3><p>Cursor and Windsurf connect via stdio transport — the server runs as a local process alongside the IDE. With 11 tools instead of 130+, the Cursor agent has a minimal, clear menu. It doesn’t waste reasoning cycles on tool selection or get confused by 40 CCM-specific tools when the developer is debugging a pipeline failure.</p><p>For teams that only use specific Harness modules, HARNESS_TOOLSETS lets you filter at startup:</p><p>{</p><p>&quot;mcpServers&quot;: {</p><p>&quot;harness&quot;: {</p><p>&quot;command&quot;: &quot;npx&quot;,</p><p>&quot;args&quot;: [&quot;-y&quot;, &quot;harness-mcp-v2@latest&quot;],</p><p>&quot;env&quot;: {</p><p>&quot;HARNESS_API_KEY&quot;: &quot;pat.xxx.yyy.zzz&quot;,</p><p>&quot;HARNESS_TOOLSETS&quot;: &quot;pipelines,services,connectors&quot;</p><p>}</p><p>}</p><p>}</p><p>}</p><p>‍</p><p>The agent only sees resource types from the enabled toolsets. The rest don’t exist as far as the LLM is concerned.</p><h3>Claude Code: Prompt Templates and Multi-Project Discovery</h3><p>Claude Code excels at multi-step workflows. We leaned into that with <strong>26 prompt templates</strong> across four categories:</p><ul><li>DevOps (12): build-deploy-app, debug-pipeline-failure, create-pipeline, onboard-service, dora-metrics-review, and more</li><li>FinOps (5): optimize-costs, cloud-cost-breakdown, commitment-utilization-review, cost-anomaly-investigation, rightsizing-recommendations</li><li>DevSecOps (6): security-review, vulnerability-triage, sbom-compliance-check, supply-chain-audit, security-exemption-review, access-control-audit</li><li>Harness Code (3): code-review, pr-summary, branch-cleanup</li></ul><p>Each prompt template encodes a multi-step workflow the agent can execute. debug-pipeline-failure doesn’t just fetch an execution — it calls harness_diagnose, follows chained failures, and produces a root cause analysis with actionable fixes.</p><p>The v2 server also supports multi-project workflows without hardcoded environment variables. An agent can dynamically discover the account structure, then scope subsequent calls with org_id and project_id parameters. No configuration changes needed.</p><h3>URL Context Awareness</h3><p>Every tool accepts an optional url parameter. Paste a Harness UI URL, a pipeline page, an execution log, a dashboard, and the server automatically extracts the account, org, project, and resource identifiers. The agent gets context without the developer having to specify it manually.</p><h3>Harness Skills: From MCP Tools to Guided Workflows</h3><p>Reducing tool count solves the context efficiency problem. But developers don’t just need fewer tools — they need tools that know how to chain together into real workflows. That’s where <strong>Harness Skills</strong> come in.</p><p>The v2 server ships with a companion skills layer (<a href="https://github.com/thisrohangupta/harness-skills">github.com/thisrohangupta/harness-skills</a>) that turns raw MCP tool access into guided, multi-step workflows. Skills are IDE-native agent instructions that teach the AI how to use the MCP server effectively — without the developer having to explain Harness concepts or orchestration patterns.</p><h3>How Skills Work</h3><p>Skills operate at three levels:</p><h4>Level 1: Shared Agent Instructions</h4><p>Every IDE gets a base instruction file, loaded automatically when the agent starts:</p><ul><li><strong>CLAUDE.md</strong> for Claude Code (auto-loaded)</li><li><strong>AGENTS.md</strong> for OpenAI Codex / generic agents</li><li><strong>.cursor/rules/harness.mdc</strong> for Cursor (auto-loaded as project rule)</li><li><strong>.github/copilot-instructions.md</strong> for VS Code GitHub Copilot</li></ul><p>These files teach the agent: what the 11 tools do, how Harness scoping works (account → org → project), dependency ordering (always verify referenced resources exist before creating dependents), and how to extract context from Harness UI URLs.</p><h4>Level 2: Prompt Templates (Server-Side)</h4><p>The <strong>26 MCP prompt templates</strong> registered directly in the server. Any MCP client can invoke them. They encode multi-step workflows with phase gates, e.g., build-deploy-app structures a 4-phase workflow (clone → scan → CI pipeline → deploy) with explicit “do not proceed until this step is done” checkpoints.</p><h4>Level 3: Individual Skills (Slash Commands)</h4><p>Specialized SKILL.md files that function as slash commands in the IDE. Each skill includes YAML frontmatter (trigger phrases, metadata), phased instructions, worked examples, performance notes, and troubleshooting steps.</p><ul><li><strong>Pipeline &amp; Execution: </strong>/create-pipeline, /run-pipeline, /debug-pipeline, /create-trigger, /create-template, /migrate-pipeline</li><li><strong>Infrastructure: </strong>/create-service, /create-environment, /create-infrastructure, /create-connector, /create-secret</li><li><strong>Access Control: </strong>/manage-users, /manage-roles</li><li><strong>Specialized: </strong>/analyze-costs, /audit-report, /chaos-experiment, /create-policy</li></ul><h3>The Interaction Pattern</h3><p>Without skills, a developer says “deploy my Node.js app” and the agent has to figure out the right Harness concepts, the correct ordering, and the proper API calls from scratch. With skills, the flow is:</p><ul><li>IDE auto-loads shared instructions (tool reference, scoping rules, dependency ordering)</li><li>Agent matches intent to a skill via trigger phrases in skill descriptions</li><li>Skill provides ordered, phase-gated execution steps (what to check, what to ask, what to generate)</li><li>MCP server executes the actual harness_list / harness_create / harness_execute calls</li></ul><h3>Performance Benefits</h3><p>The skills layer delivers three measurable improvements:</p><h4>Fewer Round-Trips</h4><p>Without skills, the agent typically needs 3–5 exploratory tool calls to understand Harness’s resource model before starting real work. Skills encode this knowledge upfront — the agent knows to check for existing connectors before creating a pipeline, to verify environments exist before deploying, and to use harness_describe for schema discovery instead of trial-and-error.</p><h4>Correct Ordering on First Attempt</h4><p>Harness resources have strict dependency chains (connector → secret → service → environment → infrastructure → pipeline → trigger). Skills encode the <strong>7-step “Deploy New Service”</strong> and <strong>8-step “New Project Onboarding”</strong> workflows as ordered sequences. The agent doesn’t discover dependencies through failures, it follows the prescribed order.</p><h4>Reduced Token Waste</h4><p>Each failed API call and retry burns tokens. Skills eliminate the most common failure modes (wrong scope, missing dependencies, incorrect parameter formats) by teaching the agent the patterns before execution. The combination of 11 tools (minimal context overhead) plus skills (minimal wasted calls) means more of the context window is available for the developer’s actual task.</p><h3>IDE-Specific Integration</h3><p>The first Harness MCP server (<a href="https://github.com/harness/mcp-server">harness/mcp-server</a>) pioneered the IDE-native pattern with a review-mcp-tool command that works across Cursor, Claude Code, and Windsurf via symlinked definitions:</p><ul><li>.claude/commands/ → Claude Code slash commands</li><li>.cursor/commands/ → Cursor Agent commands</li><li>.windsurf/workflows/ → Windsurf workflows</li></ul><p>One canonical definition in .harness/commands/, symlinked to all three. Update once, propagate everywhere.</p><p>The v2 skills layer extends this pattern from developer-tool commands to <strong>full DevOps workflows</strong>, the same “define once, deploy to every IDE” architecture, applied to pipeline creation, deployment debugging, cost analysis, and security review.</p><h3>Operational Safety: Designed for Production</h3><p>MCP servers that can create, update, and delete resources need safety guardrails. We built them in from the start.</p><p><strong>Human-in-the-loop confirmation: </strong>All write operations use MCP elicitation to request explicit user confirmation before executing. The agent presents what it intends to do; the developer approves or rejects.</p><p><strong>Fail-closed destructive operations: </strong>harness_delete is blocked entirely if the MCP client doesn’t support elicitation. No silent deletions.</p><p><strong>Read-only mode: </strong>Set HARNESS_READ_ONLY=true for shared environments, demos, or when you want agents to observe but not act.</p><p><strong>Secrets safety: </strong>The secret resource type exposes metadata (name, type, org, project) but never the secret value itself.</p><p><strong>Rate limiting and retries: </strong>Configurable rate limits (default: 10 req/s), automatic retries with backoff for transient failures, and bounded pagination to prevent runaway list operations.</p><h3>Deployment: Local to Team-Scale</h3><p>The v2 server supports two transports:</p><ul><li>Stdio (default): Direct integration with Claude Desktop, Cursor, Windsurf, Gemini CLI. Zero network configuration.</li><li>Streamable HTTP: Session-based remote deployment. Kubernetes manifests included. Sessions reaped after 30 minutes. CORS restricted. Rate limited to 60 req/min per IP.</li></ul><p>For team deployments, the HTTP transport is compatible with MCP gateways like Portkey, LiteLLM, and Envoy-based proxies, enabling shared control planes with centralized auth, observability, and policy enforcement.</p><p># Local (Cursor, Claude Code)</p><p>npx harness-mcp-v2@latest</p><p># Remote (team deployment)</p><p>npx harness-mcp-v2@latest http --port 3000</p><p># Docker</p><p>docker run -e HARNESS_API_KEY=pat.xxx.yyy.zzz harness-mcp-v2</p><h3>Why This Architecture Matters</h3><p>The shift from 130+ tools to 11 isn’t about simplification for its own sake. It’s about recognizing that <strong>the best MCP servers are capability-oriented agent interfaces, not API mirrors</strong>.</p><p>Building the first Harness MCP server taught us the same lesson the broader ecosystem is learning: when you expose one tool per API endpoint, you’re asking the LLM to be a routing layer. You’re consuming context on definitions that could be used for reasoning. And you’re fighting against the LLM’s actual strengths, reasoning, planning, and multi-step problem solving, by forcing it to do something a switch statement does better. That first server made the cost concrete. The v2 is our answer.</p><p>The registry pattern inverts this. The tool vocabulary is stable: 11 verbs today, 11 verbs when Harness ships 50 more resource types. The registry is extensible. The skills layer is composable. The LLM reasons about <em>what</em> to do, and the server handles <em>how</em> to do it. That’s not just an efficiency win — it’s the correct division of labor between an LLM and a server.</p><p>This is the pattern we think more MCP servers should adopt, especially platforms with broad API surfaces. The <a href="https://modelcontextprotocol.io/docs/learn/architecture">MCP specification itself</a> is built on the idea that servers expose capabilities, not endpoints. We took that literally.</p><h3>Real Life Use Cases</h3><p>The efficiency gains from the v2 architecture translate directly into concrete, time-saving use cases for developers operating within their IDEs. The combination of a minimal tool surface (11 tools), deep resource knowledge (125+ resource types), and pre-encoded workflows (Harness Skills) allows the agent to handle complex DevOps tasks with minimal guidance.</p><p>See it in action:</p><p>Some other use cases:</p><p><strong>Debug a Failed </strong><a href="https://www.harness.io/products/continuous-integration"><strong>CI Pipeline</strong></a>: Get root cause and logs for a pipeline run.</p><p><strong>Onboard New Service</strong>: Create a <a href="https://developer.harness.io/docs/continuous-delivery/x-platform-cd-features/services/services-overview/">Service</a>, <a href="https://developer.harness.io/docs/continuous-delivery/x-platform-cd-features/environments/environment-overview/">Environment</a>, Infrastructure, and initial <a href="https://developer.harness.io/docs/category/connectors/">Connector</a>.</p><p><strong>Review </strong><a href="https://www.harness.io/products/cloud-cost-management"><strong>Cloud Cost Anomaly</strong></a>: Investigate a sudden spike in cloud spend.</p><p><strong>Check Compliance Status</strong>: Verify a service’s <a href="https://www.harness.io/harness-devops-academy/what-is-a-software-bill-of-materials-sbom">SBOM</a> compliance against OPA policies.</p><p><strong>Deploy App to Prod</strong>: Execute a <a href="https://www.harness.io/harness-devops-academy/what-is-a-canary-deployment">canary deployment</a> pipeline.</p><h3>Get Started</h3><p>npx harness-mcp-v2@latest</p><p>Configure with your Harness PAT (account ID is auto-extracted):</p><p>HARNESS_API_KEY=pat.&lt;accountId&gt;.&lt;tokenId&gt;.&lt;secret&gt;</p><p>Full source: <a href="https://github.com/thisrohangupta/harness-mcp-v2">github.com/thisrohangupta/harness-mcp-v2</a></p><p>Official Harness MCP Server: <a href="https://github.com/harness/mcp-server">github.com/harness/mcp-server</a></p><p>— -</p><h3>FAQs</h3><h4>What is the Harness MCP server?</h4><p>The Harness MCP server is an MCP-compatible server that lets AI agents interact with Harness resources using a small set of generic tools.</p><h4>Why does MCP tool count matter?</h4><p>Each exposed tool adds metadata to the model context. A smaller tool surface leaves more room for reasoning and task execution.</p><h4>How is the Harness MCP server different from a traditional MCP server?</h4><p>Instead of exposing one tool per API endpoint, it uses 11 generic tools plus a registry that maps resource types to the correct API operations.</p><h4>Which AI clients work with the Harness MCP server?</h4><p>The post mentions Cursor, Claude Code, Claude Desktop, Windsurf, Gemini CLI, and other MCP-compatible clients.</p><h4>Is the Harness MCP server safe for production use?</h4><p>The design includes write confirmations, fail-closed delete behavior, read-only mode, and controls for retries, rate limiting, and deployment transport.</p><p>‍</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=420e0713e84f" width="1" height="1" alt=""><hr><p><a href="https://medium.com/harness-engineering/architecting-mcp-for-ai-agents-lessons-from-our-redesign-420e0713e84f">Architecting MCP for AI Agents: Lessons from Our Redesign</a> was originally published in <a href="https://medium.com/harness-engineering">Harness Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Powering Harness Executions Page: Inside Our Flexible Filters Component]]></title>
            <link>https://medium.com/harness-engineering/powering-harness-executions-page-inside-our-flexible-filters-component-ef1f403fc396?source=rss----c7a8b2228e23---4</link>
            <guid isPermaLink="false">https://medium.com/p/ef1f403fc396</guid>
            <dc:creator><![CDATA[Harness Engineering]]></dc:creator>
            <pubDate>Tue, 17 Feb 2026 08:55:42 GMT</pubDate>
            <atom:updated>2026-02-17T08:55:41.979Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aBwfusJ-ukuh6Guu1nNs8A.png" /></figure><p>Author: <a href="https://www.harness.io/authors/sayantan-mondal">Sayantan Mondal</a><br>Filtering data is at the heart of developer productivity. Whether you’re looking for failed builds, debugging a service or analysing deployment patterns, the ability to quickly slice and dice execution data is critical.</p><p>At Harness, users across <a href="https://www.harness.io/products/continuous-integration">CI</a>, <a href="https://www.harness.io/products/continuous-delivery">CD</a> and other modules rely on filtering to navigate complex execution data by status, time range, triggers, services and much more. While our legacy filtering worked, it had major pain points — hidden drawers, inconsistent behaviour and lost state on refresh — that slowed both developers and users.</p><p>This blog dives into how we built a <strong>new Filters component system</strong> in React: a reusable, type-safe and feature-rich framework that powers the filtering experience on the <a href="https://developer.harness.io/docs/continuous-delivery/x-platform-cd-features/executions/execution-history/">Execution Listing page</a> (and beyond).</p><h3>Prefer Watching? Here’s the Talk</h3><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2F-sqn22FEbMk%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D-sqn22FEbMk&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2F-sqn22FEbMk%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/49d72a99630a32350dbd90a14efb3729/href">https://medium.com/media/49d72a99630a32350dbd90a14efb3729/href</a></iframe><h3>The Starting Point: Challenges with Our Legacy Filters</h3><p>Our old implementation revealed several weaknesses as Harness scaled:</p><ul><li><strong>Poor Discoverability and UX:</strong> Filters were hidden in a side panel, disrupting workflow and making applied filters <strong>non-glanceable</strong>. Users didn’t get <strong>feedback</strong> until the filter was applied/saved.</li><li><strong>Inconsistency Across Modules: </strong>Custom logic in modules like <strong>CI and CD</strong> led to confusing behavioural differences.</li><li><strong>High Developer Overhead:</strong> Adding new filters was cumbersome, requiring edits to multiple files with <strong>brittle boilerplate</strong>.</li></ul><p>These problems shaped our <strong>success criteria</strong>: discoverability, smooth UX, consistent behaviour, reusable design and decoupled components.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*uNQh8DiSRwz_EDuq.png" /></figure><p>Legacy Filters UI</p><h3>The Evolution of Filters: A Design Journey</h3><p>Building a truly reusable and powerful filtering system required exploration and iteration. Our journey involved several key stages and learning from the pitfalls of each:</p><h3>Iteration 1: React Components (Conditional Rendering)</h3><p>Shifted to React functional components but kept logic centralised in the FilterFramework. Each filter was conditionally rendered based on visibleFilters array. Framework fetched filter options and passed them down as props.</p><pre>COMPONENT FilterFramework:<br>    STATE activeFilters = {}<br>    STATE visibleFilters = []<br>    STATE filterOptions = {}<br>    <br>    ON visibleFilters CHANGE:<br>        FOR EACH filter IN visibleFilters:<br>            IF filterOptions[filter] NOT EXISTS:<br>                options = FETCH filterData(filter)<br>                filterOptions[filter] = options<br>    <br>    ON activeFilters CHANGE:<br>        makeAPICall(activeFilters)<br>    <br>    RENDER:<br>        &lt;AllFilters setVisibleFilters={setVisibleFilters} /&gt;<br>        <br>        IF &#39;services&#39; IN visibleFilters:<br>            &lt;DropdownFilter <br>                name=&quot;Services&quot;<br>                options={filterOptions.services}<br>                onAdd={updateActiveFilters}<br>                onRemove={removeFromVisible}<br>            /&gt;<br>        <br>        IF &#39;environments&#39; IN visibleFilters:<br>            &lt;DropdownFilter ... /&gt;</pre><p>Pitfalls: Adding new filters required changes in multiple places, creating a maintenance nightmare and poor developer experience. The framework had minimal control over filter implementation, lacked proper abstraction and scattered filter logic across the codebase, making it neither “stupid-proof” nor scalable.</p><h3>Iteration 2: React.cloneElement Pattern</h3><p>Improved the previous approach by accepting filters as children and using React.cloneElement to inject callbacks (onAdd, onRemove) from the parent framework. This gave developers a cleaner API to add filters.</p><pre>children.forEach(child =&gt; {<br>  if (visibleFilters.includes(child.props.filterKey)) {<br>    return React.cloneElement(child, {<br>      onAdd: (label, value) =&gt; {<br>        activeFilters[child.props.filterKey].push({ label, value });<br>      },<br>      onRemove: () =&gt; {<br>        delete activeFilters[child.props.filterKey];<br>      }<br>    });<br>  }<br>});</pre><p><strong>Pitfalls:</strong> React.cloneElement is an expensive operation that causes performance issues with frequent re-renders and it’s considered an anti-pattern by the <a href="https://react.dev/reference/react/cloneElement">React team</a>. The approach tightly coupled filters to the framework’s callback signature, made prop flow implicit and difficult to debug and created type safety issues since TypeScript struggles with dynamically injected props.</p><h3>Final Solution: Context API</h3><p>The winning design uses React Context API to provide filter state and actions to child components. Individual filters access setValue and removeFilter via useFiltersContext() hook. This decouples filters from the framework while maintaining control.</p><pre>COMPONENT Filters({ children, onChange }):<br>    STATE filtersMap = {}           // { search: { value, query, state } }<br>    STATE filtersOrder = []         // [&#39;search&#39;, &#39;status&#39;]</pre><pre>    FUNCTION updateFilter(key, newValue):<br>        serialized = parser.serialize(newValue)   // Type → String<br>        filtersMap[key] = { value: newValue, query: serialized }<br>        updateURL(serialized)<br>        onChange(allValues)</pre><pre>    ON URL_CHANGE:<br>        parsed = parser.parse(urlString)          // String → Type<br>        filtersMap[key] = { value: parsed, query: urlString }</pre><pre>    RENDER:<br>        &lt;Context.Provider value={{ updateFilter, filtersMap }}&gt;<br>            {children}<br>        &lt;/Context.Provider&gt;<br>END COMPONENT</pre><p><strong>Benefits:</strong> This solution eliminated the performance overhead of cloneElement, decoupled filters from framework internals and made it easy to add new filters without touching framework code. The Context API provides clear data flow that’s easy to debug and test, with type safety through TypeScript.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*7XwuTYoQm2fnPCdS.png" /></figure><p>New Filters UI</p><h3>Inversion of Control (IoC)</h3><p>The Context API in React unlocks something truly powerful — <strong>Inversion of Control (IoC)</strong>. This design principle is about delegating control to a framework instead of managing every detail yourself. It’s often summed up by the <em>Hollywood Principle</em>: <em>“Don’t call us, we’ll call you.”</em></p><p>In React, this translates to building flexible components that let the consumer decide <strong>what</strong> to render, while the component itself handles <strong>how</strong> and <strong>when</strong> it happens.</p><p>Our <strong>Filters framework</strong> applies this principle: you don’t have to manage when to update state or synchronise the URL. You simply define your filter components and the framework orchestrates the rest — ensuring seamless, predictable updates without manual intervention.</p><h3>How Filters Inverts Control</h3><p>Our <strong>Filters framework</strong> demonstrates <em>Inversion of Control</em> in three key ways.</p><ul><li><strong>Logic via Props:</strong> The framework doesn’t know how to save filters or fetch data — the parent injects those functions. The framework decides <em>when</em> to call them, but the parent defines <em>what</em> they do.</li><li><strong>Content via Children (Composition): </strong>The parent decides which filters to render.</li><li><strong>Actions via Callbacks:</strong> The framework triggers callbacks when users type, select or apply filters, but it’s your code that decides what happens next — fetch data, update cache or send analytics.</li></ul><p>The result? A single, reusable Filters component that works across pipelines, services, deployments or repositories. By separating UI logic from business logic, we gain flexibility, testability and cleaner architecture — the true power of <strong>Inversion of Control</strong>.</p><pre>COMPONENT DemoPage:<br>    STATE filterValues<br>    FilterHandler = createFilters()</pre><pre>    FUNCTION applyFilters(data, filters):<br>        result = data<br>        IF filters.onlyActive == true:<br>            result = result WHERE item.status == &quot;Active&quot;<br>        RETURN result</pre><pre>    filteredData = applyFilters(SAMPLE_DATA, filterValues)</pre><pre>    RENDER:<br>        &lt;RouterContextProvider&gt;<br>            &lt;FilterHandler onChange = (updatedFilters) =&gt; SET filterValues = updatedFilters&gt;<br>                <br>                // Dropdown to add filters dynamically<br>                &lt;FilterHandler.Dropdown&gt;<br>                    RENDER FilterDropdownMenu with available filters<br>                &lt;/FilterHandler.Dropdown&gt;</pre><pre>                // Active filters section<br>                &lt;FilterHandler.Content&gt;<br>                    &lt;FilterHandler.Component parser = booleanParser filterKey = &quot;onlyActive&quot;&gt;<br>                        RENDER CustomActiveOnlyFilter<br>                    &lt;/FilterHandler.Component&gt;<br>                &lt;/FilterHandler.Content&gt;</pre><pre>            &lt;/FilterHandler&gt;</pre><pre>            RENDER DemoTable(filteredData)<br>        &lt;/RouterContextProvider&gt;<br>END COMPONENT</pre><h3>The URL Problem</h3><p>One of the key technical challenges in building a filtering system is <strong>URL synchronization</strong>. Browsers only understand strings, yet our applications deal with rich data types — dates, booleans, arrays and more. Without a structured solution, each component would need to manually convert these values, leading to repetitive, error-prone code.</p><p>The solution is our <strong>parser interface</strong>, a lightweight abstraction with just two methods: parse and serialize.</p><ul><li>parse converts a URL string into the type your app needs.</li><li>serialize does the opposite, turning that typed value back into a string for the URL.</li></ul><p>This bidirectional system runs automatically — parsing when filters load from the URL and serialising when users update filters.</p><pre>const booleanParser: Parser&lt;boolean&gt; = {<br>  parse: (value: string) =&gt; value === &#39;true&#39;,   // &quot;true&quot; → true<br>  serialize: (value: boolean) =&gt; String(value)  // true → &quot;true&quot;<br>}</pre><h3>FiltersMap — The State Hub</h3><p>At the heart of our framework lies the <strong>FiltersMap</strong> — a single, centralized object that holds the complete state of all active filters. It acts as the bridge between your React components and the browser, keeping UI state and URL state perfectly in sync.</p><p>Each entry in the FiltersMap contains three key fields:</p><ul><li><strong>Value</strong> — the parsed, typed data your components actually use (e.g. Date objects, arrays, booleans).</li><li><strong>Query</strong> — the serialized string representation that’s written to the URL.</li><li><strong>State</strong> — the filter’s lifecycle status: hidden, visible or actively filtering.</li></ul><p>You might ask — why store both the typed value and its string form? The answer is <strong>performance and reliability</strong>. If we only stored the URL string, every re-render would require re-parsing, which quickly becomes inefficient for complex filters like multi-selects. By storing both, we parse only once — when the value changes — and reuse the typed version afterward. This ensures type safety, faster URL synchronization and a clean separation between UI behavior and URL representation. The result is a system that’s predictable, scalable, and easy to maintain.</p><pre>interface FilterType&lt;T = any&gt; {<br>  value?: T              // The actual filter value<br>  query?: string         // Serialized string for URL<br>  state: FilterStatus    // VISIBLE | FILTER_APPLIED | HIDDEN<br>}</pre><h3>The Journey of a Filter Value</h3><p>Let’s trace how a filter value moves through the system — from user interaction to URL synchronization.</p><p>It all starts when a user interacts with a filter component — for example, selecting a date. This triggers an onChange event with a <strong>typed value</strong>, such as a Date object. Before updating the state, the parser’s serialize method converts that typed value into a <strong>URL-safe string</strong>.</p><p>The framework then updates the <strong>FiltersMap</strong> with both versions:</p><ul><li>the typed value under value and</li><li>the serialized string under query.</li></ul><p>From here, two things happen simultaneously:</p><ol><li>The onChange callback fires, passing typed values back to the parent component — allowing the app to immediately fetch data or update visualizations.</li><li>The URL updates using the serialized query string, keeping the browser’s address bar in sync and making the current filter state instantly shareable or bookmarkable.</li></ol><p>The reverse flow works just as seamlessly. When the URL changes — say, the user clicks the back button — the parser’s parse method converts the string back into a typed value, updates the FiltersMap and triggers a re-render of the UI.</p><p>All of this happens within milliseconds, enabling a smooth, <strong>bidirectional synchronization</strong> between the application state and the URL — a crucial piece of what makes the Filters framework feel so effortless.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ygmNexv69D2Z-yIN.png" /></figure><p>Journey of a Filter Value</p><h3>Conclusion</h3><p>For teams tackling similar challenges — complex UI state management, URL synchronization and reusable component design — this architecture offers a practical blueprint to build upon. The patterns used are not specific to Harness; they are broadly applicable to any modern frontend system that requires scalable, stateful and user-driven filtering.</p><p>The team’s <strong>core objectives</strong> — discoverability, smooth UX, consistent behavior, reusable design and decoupled elements — directly shaped every architectural decision. Through <strong>Inversion of Control</strong>, the framework manages the <em>when</em> and <em>how</em> of state updates, lifecycle events and URL synchronization, while developers define the <em>what</em> — business logic, API calls and filter behavior.</p><p>By treating the <strong>URL as part of the filter state</strong>, the architecture enables shareability, bookmarkability and native browser history support. The <strong>Context API</strong> serves as the control distribution layer, removing the need for prop drilling and allowing deeply nested components to seamlessly access shared logic and state.</p><p>Ultimately, <strong>Inversion of Control</strong> also paved the way for advanced capabilities such as <strong>saved filters, conditional rendering, and sticky filters</strong> — all while keeping the framework lightweight and maintainable. This approach demonstrates how clear objectives and sound architectural principles can lead to scalable, elegant solutions in complex UI systems.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ef1f403fc396" width="1" height="1" alt=""><hr><p><a href="https://medium.com/harness-engineering/powering-harness-executions-page-inside-our-flexible-filters-component-ef1f403fc396">Powering Harness Executions Page: Inside Our Flexible Filters Component</a> was originally published in <a href="https://medium.com/harness-engineering">Harness Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>