<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mykola Mizhigurskiy</title>
    <description>The latest articles on DEV Community by Mykola Mizhigurskiy (@kotyk).</description>
    <link>https://dev.to/kotyk</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3186292%2Ff76ff383-d13c-42f5-9b1c-f438e95b8036.jpg</url>
      <title>DEV Community: Mykola Mizhigurskiy</title>
      <link>https://dev.to/kotyk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kotyk"/>
    <language>en</language>
    <item>
      <title>100+ API Endpoints and Almost Zero Integration Bugs. Sharing the Framework.</title>
      <dc:creator>Mykola Mizhigurskiy</dc:creator>
      <pubDate>Tue, 18 Aug 2026 14:19:48 +0000</pubDate>
      <link>https://dev.to/kotyk/100-api-endpoints-and-almost-zero-integration-bugs-sharing-the-framework-49co</link>
      <guid>https://dev.to/kotyk/100-api-endpoints-and-almost-zero-integration-bugs-sharing-the-framework-49co</guid>
      <description>&lt;p&gt;Like most developers, I spent quite a long time suffering from integration issues that inevitably appear in any reasonably serious project: the backend changes a field, adds a new one, changes its format — and the frontend finds out only after something breaks.&lt;/p&gt;

&lt;p&gt;So, some time ago, I decided I had had enough and built my own API contract testing framework for the project I was working on. In practice, the result turned out even better than I expected: after fully implementing it, the number of integration issues between the frontend and backend dropped to almost zero.&lt;/p&gt;

&lt;p&gt;In this article, I want to share this framework with you: how I designed it, what problems I wanted to solve, how I organized schemas and contracts, and why it eventually became much more than just a collection of API tests.&lt;/p&gt;

&lt;p&gt;The stack in my case is fairly standard for a modern frontend project: &lt;strong&gt;JavaScript, TypeScript, React, and Zod&lt;/strong&gt;. But the idea itself is not tied to any particular library — Zod is simply a very convenient tool for implementing it.&lt;/p&gt;

&lt;p&gt;Before building anything, I defined the primary goal of the framework: &lt;strong&gt;automatically validate real API responses and guarantee that the backend returns exactly what the frontend expects.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That means catching changes in field types and formats, missing required fields, newly introduced unexpected fields, validation rule violations, and changes to stable response structures.&lt;/p&gt;

&lt;p&gt;And that is where the entire framework architecture begins.&lt;/p&gt;

&lt;h2&gt;
  
  
  QA Engineer vs. Software Developer
&lt;/h2&gt;

&lt;p&gt;Before we begin, I want to address what I believe is a fairly common misconception about API contract testing: &lt;strong&gt;that it should be done by developers rather than QA engineers.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This framework can be implemented and successfully used by both a QA engineer and a software developer. Its basic implementation does not require deep knowledge of the application architecture: a QA engineer can describe API contracts, build validation schemas, and use them to automatically validate API responses. In this form, the framework already fully performs its primary task — detecting contract violations between the backend and its consumer.&lt;/p&gt;

&lt;p&gt;However, when a software developer integrates this framework directly into the application architecture, the same resources can provide significantly more value. Properly designed schemas effectively become a &lt;strong&gt;single source of truth&lt;/strong&gt; for several parts of the application:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TypeScript types;&lt;/li&gt;
&lt;li&gt;contract tests;&lt;/li&gt;
&lt;li&gt;runtime validation;&lt;/li&gt;
&lt;li&gt;form validation;&lt;/li&gt;
&lt;li&gt;rules and constraints for individual fields;&lt;/li&gt;
&lt;li&gt;frontend elements that depend on those constraints, such as validation errors or character counters;&lt;/li&gt;
&lt;li&gt;API documentation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So instead of independently describing the same structure in types, tests, forms, UI, and documentation, we define it once and reuse it everywhere it is needed.&lt;/p&gt;

&lt;p&gt;That is why, for QA, this framework can be a powerful API contract testing tool, while for a developer it can also become part of the application architecture itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Framework Organization
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/api/
  ├── endpoints  # Resources required for API interaction
  |   └── users  # Business-entity folder grouping related endpoints
  |       └── getProductById/  # Named endpoint folder
  |           ├── getProductById.api.ts  # Bare reusable API function
  |           ├── getProductById.hook.ts  # UI-ready hook
  |           └── getProductById.schemas.ts  # Validation schemas and types
  └── schemas  # Business-entity definitions
      ├── common.schemas.ts  # Schemas and types reused across business entities
      └── product.schemas.ts  # Schemas and types for a specific business entity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pay attention to how the files inside each endpoint are organized. There are two reasons for this.&lt;/p&gt;

&lt;p&gt;The first is that I build this framework as an &lt;strong&gt;AI-native architecture&lt;/strong&gt; and try to make it as predictable and convenient for AI agents as possible. The more clearly responsibilities are separated and the more stable the naming patterns are, the easier it is for an agent to understand the structure, locate the correct file, and extend the existing implementation consistently.&lt;/p&gt;

&lt;p&gt;The second reason is separation of concerns. I intentionally split the implementation into several &lt;strong&gt;suffix files&lt;/strong&gt;, each with one clearly defined responsibility:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;*.api.ts&lt;/code&gt;&lt;/strong&gt; — a minimal function responsible for interacting with the API. It does not depend on React and contains only the logic required to make a request and receive a response. Because of this, it can be used independently in React hooks, contract tests, server-side code, or any other consumer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;*.hook.ts&lt;/code&gt;&lt;/strong&gt; — the React layer on top of the API function. This is where loading and error states, caching, state management, transformations, and other UI-specific logic can live.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;*.schemas.ts&lt;/code&gt;&lt;/strong&gt; — the contract of a specific endpoint: Zod schemas for runtime validation and the TypeScript types associated with them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This separation is particularly important for contract tests. A test does not need to initialize React or pull UI-specific logic along with it just to validate an API. It can call a function from &lt;code&gt;*.api.ts&lt;/code&gt; directly and validate the response using a schema from &lt;code&gt;*.schemas.ts&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Naming Conventions
&lt;/h2&gt;

&lt;p&gt;I personally consider semantics one of the most underrated parts of software development, and I take it very seriously. A good name should immediately tell you &lt;strong&gt;what entity you are looking at, what role it performs, and where it is used&lt;/strong&gt;, even before you open the file.&lt;/p&gt;

&lt;p&gt;This becomes especially important in AI-native repositories: consistent naming allows AI to infer relationships between entities more accurately, anticipate their purpose, and find the right context. That is why naming conventions in this framework are not cosmetic — they are part of the architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  Endpoints
&lt;/h3&gt;

&lt;p&gt;For endpoints, I use a simple rule:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;operation&amp;gt;&amp;lt;entity&amp;gt;&amp;lt;qualifier?&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;operation&lt;/strong&gt; comes first, followed by the &lt;strong&gt;business entity&lt;/strong&gt;, and then, when necessary, an additional qualifier describing the request.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;getProductById&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;createProduct&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;modifyProduct&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;deleteProduct&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getUserProduct&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The same naming is used for the endpoint folder and its related files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;getProductById/
  ├── getProductById.api.ts
  ├── getProductById.hook.ts
  └── getProductById.schemas.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way, all parts of a particular API request can easily be found using the same name.&lt;/p&gt;

&lt;h3&gt;
  
  
  Schemas &amp;amp; Types
&lt;/h3&gt;

&lt;p&gt;For schemas and types, I use a separate set of standard suffixes describing the role of the data in a particular request:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;QueryParams&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RequestPayload&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ResponsePayload&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the entity is a Zod schema, I simply add &lt;code&gt;Schema&lt;/code&gt; to the name.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GetProductById_QueryParams&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CreateProduct_RequestPayload&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GetProductById_ResponsePayload&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GetProductById_ResponsePayloadSchema&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a result, the name itself describes the structure:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;endpoint&amp;gt;_&amp;lt;data role&amp;gt;&amp;lt;Schema?&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;So when you see &lt;code&gt;GetProductById_ResponsePayloadSchema&lt;/code&gt;, you immediately know three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;which endpoint it belongs to;&lt;/li&gt;
&lt;li&gt;which part of the API contract it describes;&lt;/li&gt;
&lt;li&gt;that you are looking at a runtime schema rather than a TypeScript type.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Common Schemas
&lt;/h3&gt;

&lt;p&gt;Let us start with the simplest file — &lt;code&gt;common.schemas.ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Its purpose is to store small but reusable schemas that appear across many business entities. These are not standalone application models, but rather building blocks: IDs, timestamps, tracker fields, UUIDs, emails, and other standardized primitives.&lt;/p&gt;

&lt;p&gt;For example, a minimal &lt;code&gt;common.schemas.ts&lt;/code&gt; might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// Unified ID type used across the application&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Id_Schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// Unified set of fields used to track entity creation and modification&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Tracker_Schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;createdOn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;modifiedOn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These schemas are not duplicated in every business entity. Instead, they are reused through &lt;code&gt;extend()&lt;/code&gt; or directly as individual fields.&lt;/p&gt;

&lt;p&gt;This allows us to change a rule in one place and automatically propagate it throughout the application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Business Entity
&lt;/h3&gt;

&lt;p&gt;Now let us move to &lt;code&gt;product.schemas.ts&lt;/code&gt; — the file describing the &lt;code&gt;Product&lt;/code&gt; business entity. For schemas like this, I use a simple naming rule: the entity name plus the &lt;code&gt;Schema&lt;/code&gt; suffix — in our case, &lt;code&gt;Product_Schema&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There is one fundamentally important point here: &lt;strong&gt;we describe the entity as the frontend sees it, not as it exists internally on the backend&lt;/strong&gt;. The same entity can have different representations on the two sides of an API.&lt;/p&gt;

&lt;p&gt;For example, the backend model of a product might contain a &lt;code&gt;stock&lt;/code&gt; property with the exact number of units remaining in the warehouse. The store owners probably do not want to expose that information publicly, so instead of &lt;code&gt;stock: 12345&lt;/code&gt;, the frontend might receive only &lt;code&gt;inStock: true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That frontend representation is what our contract should describe.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Id_Schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Tracker_Schema&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./common.schemas&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// Product business-entity schema for runtime validation.&lt;/span&gt;
&lt;span class="c1"&gt;// Extends the shared tracker fields.&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Product_Schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Tracker_Schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Id_Schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;inStock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Product type automatically inferred from the runtime validation schema.&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;Product_Schema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;// Generic Response Schema for endpoints that return the same object shape.&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Product_GenericResponseSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Product_Schema&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An important detail here is that &lt;strong&gt;we do not define the &lt;code&gt;Product&lt;/code&gt; type separately&lt;/strong&gt;. It is automatically inferred from the same Zod schema through &lt;code&gt;z.infer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As a result, &lt;code&gt;Product_Schema&lt;/code&gt; becomes both a runtime representation of the contract and the source of compile-time typing. If we change the structure or validation rules of &lt;code&gt;Product_Schema&lt;/code&gt;, the TypeScript &lt;code&gt;Product&lt;/code&gt; type changes with it.&lt;/p&gt;

&lt;p&gt;Runtime validation and typing remain synchronized by design.&lt;/p&gt;

&lt;p&gt;Also note &lt;code&gt;Product_GenericResponseSchema&lt;/code&gt;. In real-world APIs, several endpoints often return the same representation of an entity.&lt;/p&gt;

&lt;p&gt;Instead of describing that shape again in every endpoint-specific file, I suggest defining it at the higher business-entity level and reusing it from there.&lt;/p&gt;

&lt;p&gt;One more important clarification about &lt;code&gt;Product_Schema&lt;/code&gt;: &lt;strong&gt;a business-level schema should contain every field that may appear for this entity across any endpoint&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is not an endpoint-specific contract. It is an entity-specific contract — effectively a &lt;strong&gt;single source of truth for all fields of the entity and their validation rules&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Individual endpoints then build their own contracts on top of this base schema using &lt;code&gt;pick&lt;/code&gt;, &lt;code&gt;omit&lt;/code&gt;, &lt;code&gt;extend&lt;/code&gt;, or reusable subschemas.&lt;/p&gt;

&lt;h3&gt;
  
  
  API Contract
&lt;/h3&gt;

&lt;p&gt;Once we have a canonical business entity, it is time to describe the contract of a specific endpoint. For this example, let us use &lt;code&gt;createProduct.schemas.ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Suppose the &lt;code&gt;get&lt;/code&gt;, &lt;code&gt;create&lt;/code&gt;, and &lt;code&gt;update&lt;/code&gt; endpoints all return &lt;code&gt;Product&lt;/code&gt; in the same representation. In that case, there is no reason to redefine the schema every time.&lt;/p&gt;

&lt;p&gt;We simply import the prepared &lt;code&gt;Product_GenericResponseSchema&lt;/code&gt; from &lt;code&gt;product.schemas.ts&lt;/code&gt; and use it as the basis for &lt;code&gt;CreateProduct_ResponsePayloadSchema&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Product_GenericResponseSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/api/schemas/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// Runtime validation schema for the Create Product response payload.&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CreateProduct_ResponsePayloadSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;Product_GenericResponseSchema&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;CreateProduct_ResponsePayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
  &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;CreateProduct_ResponsePayloadSchema&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;// Request payload type for Create Product.&lt;/span&gt;
&lt;span class="c1"&gt;// A Zod schema is intentionally not created because it is not used.&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;CreateProduct_RequestPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Pick&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
  &lt;span class="nx"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is another important principle here: &lt;strong&gt;do not create a Zod schema just because you can&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If some data never goes through runtime validation and the schema is not used anywhere, it only creates additional code and another entity that must be maintained.&lt;/p&gt;

&lt;p&gt;That is why a Zod schema makes sense for &lt;code&gt;ResponsePayload&lt;/code&gt;, which we are going to validate in contract tests. But if compile-time typing is enough for &lt;code&gt;RequestPayload&lt;/code&gt;, we can simply derive the required type from the existing &lt;code&gt;Product&lt;/code&gt; type using &lt;code&gt;Pick&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This way, even endpoint-specific contracts continue to derive from the same source of truth instead of creating parallel descriptions of the same structure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contract Tests
&lt;/h3&gt;

&lt;p&gt;Once the business entities and endpoint contracts have been described, the tests themselves become almost boring.&lt;/p&gt;

&lt;p&gt;All you need to do is call the API, receive the response, and validate it against the appropriate Zod schema.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getProductById_API&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/api/endpoints&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GetProductById_ResponsePayloadSchema&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/api/schemas&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;API contracts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Products&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET product by ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getProductById_API&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12345&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;GetProductById_ResponsePayloadSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toThrow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All the complexity is already contained in properly designed schemas, so adding another check usually takes only a few lines of code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Important Clarification: These Are Not API Unit Tests&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is important to understand the purpose of contract testing correctly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We are not trying to test the API itself or verify all of its business logic.&lt;/strong&gt; This is not the place for edge cases, attempts to “break” an endpoint, or checks of how the backend behaves when given invalid data.&lt;/p&gt;

&lt;p&gt;Our task is much simpler: make a valid request, receive a successful response, and verify that it matches the contract our application relies on.&lt;/p&gt;

&lt;p&gt;So keep contract tests as simple as possible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;call the API;&lt;/li&gt;
&lt;li&gt;make sure the request succeeds;&lt;/li&gt;
&lt;li&gt;validate the response against the appropriate schema.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not turn contract tests into a duplicate of the backend unit test suite — you do have one, right?&lt;/p&gt;

&lt;p&gt;There is only one thing we care about here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does the API return what its consumer expects?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Pro Tips
&lt;/h2&gt;

&lt;h3&gt;
  
  
  #1. Array Validation
&lt;/h3&gt;

&lt;p&gt;Keep in mind that Zod successfully validates an empty array even when a specific schema is defined for its elements:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;z.array(Product_Schema)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;[]&lt;/code&gt;, validation passes successfully because there is simply no element to which &lt;code&gt;Product_Schema&lt;/code&gt; can be applied.&lt;/p&gt;

&lt;p&gt;So if your test is supposed to validate not only the presence of an array but also &lt;strong&gt;the shape of its elements&lt;/strong&gt;, make sure your test data contains at least one item.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeGreaterThanOrEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Otherwise, you risk getting a false positive: the test is green, but the structure of the objects inside the array has never actually been validated.&lt;/p&gt;

&lt;h3&gt;
  
  
  #2. Catching New Fields
&lt;/h3&gt;

&lt;p&gt;In contract tests, it is important to detect not only missing or invalid fields, but also &lt;strong&gt;new unexpected fields&lt;/strong&gt; appearing in the response.&lt;/p&gt;

&lt;p&gt;That is why schemas should be validated in strict mode. Otherwise, the backend may add a new field and your test will never notice it.&lt;/p&gt;

&lt;p&gt;There are two ways to solve this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The first&lt;/strong&gt; is to explicitly enable strict validation in every Zod object schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Product_Schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Id_Schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The second option&lt;/strong&gt;, which I personally prefer, is to create a dedicated utility for contract tests.&lt;/p&gt;

&lt;p&gt;It recursively traverses the entire provided Zod schema and applies strict behavior to all object schemas, including objects nested inside arrays, other objects, unions, and so on.&lt;/p&gt;

&lt;p&gt;This keeps the schemas themselves universal while centralizing strict behavior exactly where it is needed — in contract tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  #3. Show the Received Value
&lt;/h3&gt;

&lt;p&gt;Another small detail that has a surprisingly large impact on debugging: &lt;strong&gt;the validation error should show the actual value that failed validation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A message such as “the field does not match the expected format” is often not enough, especially when tests operate on large responses or deeply nested structures.&lt;/p&gt;

&lt;p&gt;I recommend configuring error reporting so that, along with the field path and the reason for the failure, it also prints the value that triggered the error.&lt;/p&gt;

&lt;p&gt;Instead of something abstract like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Invalid format at products[3].id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;it is much more useful to get something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Invalid format at products[3].id
Received: "abc-123"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It may seem like a small detail, but in practice it significantly reduces the time required to investigate failing tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  #4. Stable Data
&lt;/h3&gt;

&lt;p&gt;Sometimes an API contains endpoints that return &lt;strong&gt;stable data&lt;/strong&gt; — values that are not expected to change regularly.&lt;/p&gt;

&lt;p&gt;A typical example is a list of statuses for a business entity.&lt;/p&gt;

&lt;p&gt;In this case, it can be useful not only to validate the general response shape but also to &lt;strong&gt;explicitly lock down the expected set of values using literals&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ProductStatus_Schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Pending&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;In Progress&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Done&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first glance, this may look like unnecessary overhead. In practice, however, it can be extremely useful — especially when frontend behavior depends directly on specific statuses.&lt;/p&gt;

&lt;p&gt;For example, different statuses may display different UI, enable different actions, or trigger different business logic.&lt;/p&gt;

&lt;p&gt;In such a case, introducing a new status or renaming an existing one is no longer just a data change. It is &lt;strong&gt;a contract change that can potentially affect the frontend&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you cannot be completely sure that the backend team will always notify you about such changes in time, contract tests can serve as an additional safety net.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonuses
&lt;/h2&gt;

&lt;p&gt;At the beginning of the article, I mentioned that a software developer can get much more from this framework than contract tests alone.&lt;/p&gt;

&lt;p&gt;Since the Zod schemas have already become our &lt;strong&gt;single source of truth&lt;/strong&gt;, we can reuse them directly in application code.&lt;/p&gt;

&lt;p&gt;Here are a few practical examples.&lt;/p&gt;

&lt;h3&gt;
  
  
  #1. API Typing
&lt;/h3&gt;

&lt;p&gt;Since TypeScript types are automatically inferred from our Zod schemas, the same types can be used directly in the API layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getProductByIdApi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;GetProductById_ResponsePayload&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/products/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means API typing and API validation are based on the same source of truth.&lt;/p&gt;

&lt;p&gt;Change the schema, and the type used by the application code changes with it.&lt;/p&gt;

&lt;h3&gt;
  
  
  #2. Runtime Validation of Local Storage Data
&lt;/h3&gt;

&lt;p&gt;Suppose a user is creating a product and we store a draft of the form in &lt;code&gt;localStorage&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After the page reloads, we want to restore it — but before putting the data back into the form, we want to make sure it still conforms to our contract.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rawDraft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;productDraft&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawDraft&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsedDraft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawDraft&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ProductDraft_Schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsedDraft&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Invalid JSON — ignore the stored draft&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives the data from &lt;code&gt;localStorage&lt;/code&gt; two validation layers: first, we ensure it is valid JSON; then Zod checks its structure and values before it is allowed back into the form.&lt;/p&gt;

&lt;h3&gt;
  
  
  #3. Form Validation
&lt;/h3&gt;

&lt;p&gt;The same Zod schemas can be integrated directly with popular form libraries.&lt;/p&gt;

&lt;p&gt;For example, React Hook Form provides a Zod resolver:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useForm&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;zodResolver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Product_Schema&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a result, rules such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;do not need to be described again in the form.&lt;/p&gt;

&lt;p&gt;The schema that defines the API contract and TypeScript type also defines the form validation rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  #4. Using Schema Constraints in the UI
&lt;/h3&gt;

&lt;p&gt;A Zod schema contains not only information about the field type, but also its rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the UI displays a character counter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;127 / 500
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;there is no reason to hardcode &lt;code&gt;500&lt;/code&gt; separately inside the React component.&lt;/p&gt;

&lt;p&gt;We can get it directly from the schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;maxLength&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Product_Schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxLength&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and use that value directly in the UI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CharacterCounter&lt;/span&gt;
  &lt;span class="na"&gt;current&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;maxLength&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means &lt;code&gt;500&lt;/code&gt; exists in exactly one place — &lt;code&gt;Product_Schema&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Form validation, runtime validation, and the UI all use the same rule.&lt;/p&gt;

&lt;h3&gt;
  
  
  #5. Third-Party Service Integration
&lt;/h3&gt;

&lt;p&gt;But why are we talking only about the classic frontend ↔ backend integration?&lt;/p&gt;

&lt;p&gt;Let us look at this framework from another angle.&lt;/p&gt;

&lt;p&gt;Suppose your application integrates with a &lt;strong&gt;third-party API&lt;/strong&gt;. Your code directly depends on the responses returned by an external service, but you have neither a direct communication channel with its team nor any guarantee that you will receive timely notifications about API changes.&lt;/p&gt;

&lt;p&gt;In this scenario, contract testing can become even more valuable.&lt;/p&gt;

&lt;p&gt;Your tests effectively turn into an early-warning system: if the external service changes the response structure, removes a field, adds a new one, or changes its format, you can find out automatically — before that change turns into a problem for your users.&lt;/p&gt;

&lt;p&gt;In other words, the same framework that helps reduce communication and integration issues in an internal frontend ↔ backend setup can, in the case of a third-party API, partially &lt;strong&gt;compensate for the complete absence of that communication&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  #6. AI Compatibility
&lt;/h3&gt;

&lt;p&gt;I put a lot of effort into &lt;strong&gt;standardization, structural consistency, and semantics&lt;/strong&gt; while designing this framework.&lt;/p&gt;

&lt;p&gt;And this is where we get to reap the benefits.&lt;/p&gt;

&lt;p&gt;The more predictable the repository architecture is, the easier it is for AI to understand its patterns and extend them correctly.&lt;/p&gt;

&lt;p&gt;If the framework is implemented consistently, AI no longer has to decide every time where to create files, how to name schemas, how to group endpoints, or how to structure contract tests.&lt;/p&gt;

&lt;p&gt;The repository already contains a pattern that the agent only needs to recognize and reproduce.&lt;/p&gt;

&lt;p&gt;In practice, this means that when a new API module appears, I can simply give the AI a link to the Swagger specification.&lt;/p&gt;

&lt;p&gt;From there, it can parse the API specification, identify business entities and endpoints, and generate the module according to the existing structure: schemas, types, abstractions, and contract tests.&lt;/p&gt;

&lt;p&gt;And yes, this has been tested in practice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Implementing a new API module with around 10 endpoints, including testing, takes about 16 minutes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So standardization here improves more than developer experience.&lt;/p&gt;

&lt;p&gt;It makes the repository genuinely &lt;strong&gt;AI-native&lt;/strong&gt;: instead of repeatedly explaining to an agent &lt;em&gt;how&lt;/em&gt; another module should be implemented, we give it an architecture where the correct implementation pattern is already encoded in the structure and semantics of the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A few numbers.&lt;/p&gt;

&lt;p&gt;While working with a large API containing more than 100 endpoints, this framework allowed me to reduce structural integration bugs to almost zero, cut the reaction time to API changes to within a single day, and catch around 100 integration issues in just three months — without requiring additional communication with the backend team.&lt;/p&gt;

&lt;p&gt;I consider that a fairly strong result.&lt;/p&gt;

&lt;p&gt;At this point, the framework solves one of the classic problems of software development quite well: the constant need to stay synchronized with API changes and detect situations where the actual contract starts diverging from what the application expects.&lt;/p&gt;

&lt;p&gt;Overall, I am happy with the result.&lt;/p&gt;

&lt;p&gt;The framework solves all the core problems I originally designed it for, requires relatively little additional code, and provides significantly more value than API validation alone.&lt;/p&gt;

&lt;p&gt;I would be very interested to hear your thoughts.&lt;/p&gt;

&lt;p&gt;Do you use anything similar in your projects?&lt;/p&gt;

&lt;p&gt;Where do you think this approach works well — and where do you think it starts to break down?&lt;/p&gt;

</description>
      <category>api</category>
      <category>testing</category>
      <category>software</category>
      <category>architecture</category>
    </item>
    <item>
      <title>I Tested Two AI Context Architectures: Context Discovery vs Prompt Engine. Here’s What I Learned.</title>
      <dc:creator>Mykola Mizhigurskiy</dc:creator>
      <pubDate>Tue, 11 Aug 2026 18:51:23 +0000</pubDate>
      <link>https://dev.to/kotyk/i-tested-two-ai-context-architectures-context-discovery-vs-prompt-engine-heres-what-i-learned-2286</link>
      <guid>https://dev.to/kotyk/i-tested-two-ai-context-architectures-context-discovery-vs-prompt-engine-heres-what-i-learned-2286</guid>
      <description>&lt;p&gt;For the last few months, I have been building my own AI framework for software development. It contains instructions for different engineering activities: implementing features, refactoring, writing tests, debugging, working with APIs, reviewing code, and many smaller tasks. As the framework grew beyond 100,000 characters of instructions, one problem became increasingly important: how do I give an AI agent exactly the instructions it needs without loading the entire framework into its context?&lt;/p&gt;

&lt;p&gt;While working on this problem, I ended up testing two different architectures: &lt;em&gt;Agent-Driven Context Discovery&lt;/em&gt; and a &lt;em&gt;Prompt Engine with Context Compilation&lt;/em&gt;. Both allow you to keep instructions modular and load only what is relevant to the current task. The major difference is who is responsible for assembling that context.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Agent-Driven Context Discovery&lt;/em&gt; leaves this responsibility to the agent. You give it an entry point, usually a base instruction file, and that file references other instructions. The agent reads those files, follows their references, and gradually discovers the context required for the task.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Prompt Engine with Context Compilation&lt;/em&gt; moves most of this work into deterministic code. The agent still selects the type of activity it is about to perform, but once that decision is made, the Prompt Engine deterministically resolves the required instructions and compiles them into a ready-to-use context. The agent no longer has to manually discover and retrieve every instruction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shared Foundation: Intent Router / Playbook
&lt;/h2&gt;

&lt;p&gt;Both architectures still need a mechanism that connects a task with the appropriate instructions. I call mine &lt;strong&gt;a playbook&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The playbook is essentially an intent router. It describes the types of activities the framework supports and maps those activities to the instruction sets they require. Implementing functionality, refactoring code, writing tests, debugging, and reviewing code are examples of activities that can have different instruction dependencies.&lt;/p&gt;

&lt;p&gt;With &lt;em&gt;Context Discovery&lt;/em&gt;, this routing can live inside the instruction graph itself. The base instruction tells the agent where to go, and the agent follows the appropriate path.&lt;/p&gt;

&lt;p&gt;With my &lt;em&gt;Prompt Engine&lt;/em&gt;, I use an MCP tool called &lt;code&gt;getInstructions&lt;/code&gt;. It accepts typed activities and uses them to programmatically determine which instructions should be compiled. The agent therefore makes one high-level decision — &lt;em&gt;what am I about to do?&lt;/em&gt; — while deterministic code handles the low-level question — &lt;em&gt;which exact instructions does that activity require?&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach 1: Agent-Driven Context Discovery
&lt;/h2&gt;

&lt;p&gt;This was the first architecture I implemented. I split my framework into many small instruction files and created references between them. The agent received a base instruction as its entry point and was expected to follow those references whenever additional instructions were required.&lt;/p&gt;

&lt;p&gt;Conceptually, it looked something like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Task → Base Instructions → Agent → Referenced Instructions → More Instructions → Work&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I liked this approach initially because it was extremely easy to build. The instruction system remained modular, individual files could be reused, and adding a new rule usually meant adding or updating a reference. There was no separate infrastructure responsible for assembling prompts because the agent itself effectively acted as the resolver.&lt;/p&gt;

&lt;p&gt;After using this architecture for some time, however, I started noticing several problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 1: Reasoning Overhead
&lt;/h3&gt;

&lt;p&gt;Every time the agent encounters a reference, it has to understand why that instruction exists, decide whether it should follow it, retrieve the file, process it, and potentially repeat the process. These are small decisions, but they are still decisions made by the model instead of deterministic operations performed by software.&lt;/p&gt;

&lt;p&gt;I increasingly disliked spending the agent's reasoning capacity on this kind of plumbing. I want the model thinking about architecture, requirements, edge cases, and implementation decisions. Resolving an instruction dependency does not require intelligence if I can already express that dependency in code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 2: Reliability
&lt;/h3&gt;

&lt;p&gt;I repeatedly saw agents skip files even when another instruction explicitly told them to read those files. It did not happen every time, but it happened often enough that I could not consider instruction delivery deterministic.&lt;/p&gt;

&lt;p&gt;This creates an unpleasant failure mode. Your agent may produce perfectly reasonable code while silently missing one of the rules it was supposed to follow. The problem is not that the model misunderstood an instruction — it never loaded that instruction in the first place.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 3: Tool Usage
&lt;/h3&gt;

&lt;p&gt;Reading each instruction file requires a tool call. With more than 100,000 characters distributed across many files, the agent could make a surprising number of calls just to prepare itself for the actual task.&lt;/p&gt;

&lt;p&gt;I repeatedly hit Copilot’s maximum tool-call limit while the agent was navigating instruction files. When that happened, Copilot stopped before the actual task was completed. That was a strong signal that I needed a different architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 4: Inefficient Context Usage
&lt;/h3&gt;

&lt;p&gt;Another problem is that routing instructions remain in the agent's context long after they have served their purpose. Instructions such as "read this file," "for this activity, also read that file," or "if this condition applies, load these additional instructions" are useful during context discovery, but provide little value once the agent starts working on the actual task. From that point on, they simply consume space in the context window.&lt;/p&gt;

&lt;p&gt;This problem also has the potential to grow over time. If the agent occasionally fails to load the right files for certain activities, the natural response is to make the routing instructions more explicit: add explanations, introduce more specific terminology, describe additional conditions, or repeat important references. That may improve routing reliability, but it also increases the amount of temporary information that remains in the context throughout the task.&lt;/p&gt;

&lt;p&gt;In other words, the more effort you put into making agent-driven context discovery reliable, the more context you may end up spending on instructions that become useless immediately after discovery is complete.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach 2: Prompt Engine with Context Compilation
&lt;/h2&gt;

&lt;p&gt;My second implementation moved instruction resolution out of the agent and into code.&lt;/p&gt;

&lt;p&gt;The framework still contains dozens of small, reusable instruction files. The difference is that the agent no longer needs to navigate them one by one. A script resolves the dependencies, collects the required files, and returns the complete instruction set in a single operation.&lt;/p&gt;

&lt;p&gt;The architecture now looks closer to this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Task → Activity → Prompt Engine → Compiled Context → Agent → Work&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The change may sound small, but the practical improvement was substantial. Once the agent selects an activity, the rest of the process is deterministic. If activity X requires instructions A, B, C, and D, the Prompt Engine delivers all four. There is no intermediate step where the agent can forget to open C or decide that D is unnecessary.&lt;/p&gt;

&lt;p&gt;Context delivery also became much faster. Instead of making a sequence of tool calls, reading files one by one, and reasoning about what to retrieve next, the agent makes a single request. The Prompt Engine resolves the instruction graph programmatically and returns the compiled context in a single operation. This removes both the latency of repeated tool calls and the reasoning overhead involved in navigating the instruction structure.&lt;/p&gt;

&lt;p&gt;Another important improvement was eliminating a practical limitation I repeatedly hit with Copilot: the maximum number of tool calls available to the agent. With Context Discovery, the agent regularly exhausted this limit while navigating and reading instruction files, which caused Copilot to stop before the task was completed. After moving instruction resolution into the Prompt Engine, instruction delivery requires only one tool call, so this failure mode effectively disappeared from my workflow.&lt;/p&gt;

&lt;p&gt;In practice, I got three immediate benefits: the required instructions started reaching the context reliably, context assembly became much faster, and instruction discovery stopped consuming the agent's limited tool calls. Combined, these changes produced a very noticeable improvement in the overall speed and reliability of my workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 1: Your Prompt Engine Becomes a Mini-Product
&lt;/h3&gt;

&lt;p&gt;The first downside is additional infrastructure. In my case, the Prompt Engine requires a custom MCP server that exposes the tools the agent uses to request its instructions. Once you introduce this layer, you effectively have another small software product living inside your repository.&lt;/p&gt;

&lt;p&gt;Like any other software, it needs maintenance. You need to keep the implementation clean, handle edge cases, and ideally cover its critical behavior with automated tests. As the instruction system evolves, the Prompt Engine has to evolve with it.&lt;/p&gt;

&lt;p&gt;This is simply the price of moving from a lightweight instruction structure to a more sophisticated architecture. You gain more deterministic and reliable context delivery, but you also introduce another component that you are responsible for maintaining.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 2: You Can Accidentally Create Two Sources of Truth
&lt;/h3&gt;

&lt;p&gt;Another challenge is instruction representation. The format that is convenient for programmatic processing is not necessarily the format that is convenient for a human. For example, the Prompt Engine may work better with structured JSON, while I would much rather read, write, review, and maintain the same instructions as Markdown.&lt;/p&gt;

&lt;p&gt;The obvious solution is to keep both representations, but that immediately creates another problem: they can drift apart. If JSON says one thing and Markdown says another, you effectively have two sources of truth and can no longer be certain which representation describes the actual behavior of the system.&lt;/p&gt;

&lt;p&gt;A better architecture needs one canonical source and a deterministic way to generate the other representation from it. Ideally, the two formats should also remain structurally similar enough that you can debug the agent-facing JSON while reading the human-friendly Markdown. Otherwise, the convenience of having two representations can quickly turn into another maintenance problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 3: Compiled Instructions Can Pollute the Context
&lt;/h3&gt;

&lt;p&gt;A Prompt Engine can also make context usage worse if the routing rules are not strict enough. Imagine that the current session already contains the instructions for API engineering, but the agent does not clearly understand that those instructions should not be requested again. It may call the instruction tool a second time and inject another full copy of the same compiled context into the conversation.&lt;/p&gt;

&lt;p&gt;If your instruction sets are large, this can become surprisingly expensive. Instead of optimizing context usage, you may end up filling the context window with repeated copies of the same material. The more often the agent re-requests instructions, the faster this duplication compounds.&lt;/p&gt;

&lt;p&gt;My recommendation is to add a hard guardrail at the infrastructure level. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Track which instruction sets have already been delivered during the current session, and if the agent requests the same one again, do not return the full instruction set.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Return a short message saying that the instruction is already present in the current context and should be reused.&lt;/p&gt;

&lt;p&gt;I also &lt;strong&gt;make this failure mode explicit&lt;/strong&gt;. If the agent believes the required instruction is actually missing despite the guardrail, it should treat that as a critical inconsistency, stop, and report the problem to the user rather than silently requesting or reconstructing the same context again.&lt;/p&gt;

&lt;p&gt;This is one of those cases where a small deterministic safeguard can prevent a large amount of unnecessary context duplication.&lt;/p&gt;

&lt;h3&gt;
  
  
  Note: There Is Still One Non-Deterministic Step
&lt;/h3&gt;

&lt;p&gt;This architecture does not make everything deterministic. The agent still needs to select the correct activity when it calls &lt;code&gt;getInstructions&lt;/code&gt;, and that selection is an LLM decision.&lt;/p&gt;

&lt;p&gt;For this reason, I think the tool interface should be strongly typed and the playbook should be designed carefully. Activity names and descriptions need to be clear enough that the model can reliably map real tasks to them. Otherwise, you simply move the failure point from instruction discovery to activity classification.&lt;/p&gt;

&lt;p&gt;I would also recommend watching this routing closely when introducing such a system. Check which activities the agent selects for real tasks and adjust the playbook when you find ambiguous cases. In my framework, my subjective estimate is that incorrect activity selection happens in less than 1% of cases, possibly even below 0.5%, so this has not been a significant practical problem for me.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;After working with both architectures, &lt;strong&gt;Prompt Engine with Context Compilation wins&lt;/strong&gt; for me by a large margin.&lt;/p&gt;

&lt;p&gt;The difference is not really about prompts or files. It is about deciding which responsibilities belong to an LLM and which belong to traditional software.&lt;/p&gt;

&lt;p&gt;An LLM is excellent at understanding intent, interpreting ambiguous requirements, reasoning about code, and making decisions where the answer cannot be reduced to a simple algorithm. A script is excellent at following predefined dependencies, assembling files, validating inputs, and producing the same result every time.&lt;/p&gt;

&lt;p&gt;Context Discovery asks the LLM to do some of both. A Prompt Engine lets me draw the boundary more deliberately.&lt;/p&gt;


&lt;div class="crayons-card c-embed"&gt;

  &lt;br&gt;
&lt;strong&gt;The Core Takeaway:&lt;/strong&gt; Whenever something can be reliably handled by deterministic software, move it out of the agent's reasoning loop.&lt;br&gt;

&lt;/div&gt;


&lt;p&gt;That experiment eventually contributed to a broader principle I now use in my &lt;a href="https://dev.to/kotyk/stop-asking-ai-to-do-a-linters-job-1cp0"&gt;Semi-Automatic AI-Native Flow with a Human in the Loop&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For me, this is becoming one of the more interesting questions in AI-native development. We spend a lot of time asking how much more responsibility we can give to AI agents. &lt;/p&gt;

&lt;p&gt;Maybe we should spend just as much time asking how much responsibility we can take away from them.&lt;/p&gt;




&lt;p&gt;What edge cases did I miss here? How are you managing large instruction sets or context windows in your own AI frameworks? Let me know in the comments!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>architecture</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Microsoft GH-300: GitHub Copilot | Tips &amp; Tricks</title>
      <dc:creator>Mykola Mizhigurskiy</dc:creator>
      <pubDate>Mon, 10 Aug 2026 19:10:40 +0000</pubDate>
      <link>https://dev.to/kotyk/microsoft-gh-300-github-copilot-tips-tricks-430o</link>
      <guid>https://dev.to/kotyk/microsoft-gh-300-github-copilot-tips-tricks-430o</guid>
      <description>&lt;p&gt;Today I passed the GitHub Copilot GH-300 exam and decided to write down my impressions right away while they are still fresh. First of all, this article is not so much a “how to pass” guide as a place where people who are currently preparing for the exam can ask questions to someone who literally took it today. If anything from my experience helps you prepare better, or at least understand what to expect, then this article has already served its purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap between preparation materials and the actual exam questions
&lt;/h2&gt;

&lt;p&gt;The first thing that genuinely surprised me was how different the information in the recommended preparation materials can be from what you actually see on the exam. A few times, my completely honest reaction was: “Where did this even come from?” Subjectively, I managed to answer some of those questions not because I had memorized the materials well, but because by the time I took the exam I had already been using Copilot quite intensively in real-world development. Sometimes practical experience helped, sometimes it was a general understanding of engineering processes, and sometimes it was simply common sense.&lt;/p&gt;

&lt;p&gt;So I would not recommend treating the official materials as a complete map of the exam. They are useful and they provide the basic structure of the topics, but they are not enough to make you confident about every question. If you already use Copilot at work, that is a serious advantage, because part of the exam tests not only whether you know the wording from the documentation, but also whether you understand how the product behaves in common scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  Copilot = Microsoft
&lt;/h2&gt;

&lt;p&gt;Another thing worth keeping in mind: Copilot is GitHub, GitHub is Microsoft, and Microsoft also means Windows and PowerShell. If, like many developers, you have spent years working on macOS and barely touching the Windows ecosystem, some questions may come as an unpleasant surprise. For example, you may need at least a basic understanding of how something is &lt;strong&gt;installed&lt;/strong&gt; or configured through PowerShell. I barely looked at things like that during my preparation because I relied too heavily on the recommended resources and hardly read any additional documentation. If I were preparing again, I would definitely close that gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do questions from the preparation materials appear on the exam?
&lt;/h2&gt;

&lt;p&gt;A separate story is my attempt to “outsmart the system.” During preparation, I collected all the questions I came across in the learning modules and the practice exam, put them into Anki, and simply memorized them. I thought it would be a decent safety net: even if the questions were not repeated word for word, at least I would have the relevant facts firmly memorized. In practice, my subjective impression is that I did not see a single question repeated exactly on the real exam, and only one or two vaguely resembled something I had learned this way. So I would not recommend memorizing the correct answers; it is much more useful to use those questions as a way to check whether you actually understand the topic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Question formats
&lt;/h2&gt;

&lt;p&gt;Another mistake in my expectations about the GH-300 was assuming that the exam would mostly follow the classic format: one question, four answers, one correct choice. In reality, there are more formats. Some questions require you to choose two correct answers out of four or three out of five, some ask you to match items between two columns, and some require you to arrange several steps in the correct order. Personally, I found the last type to be one of the trickiest because it is no longer enough to “roughly remember the correct answer” — you need to understand the process itself.&lt;/p&gt;

&lt;p&gt;Statistically, you can feel the difference too. In a standard multiple-choice question with four options, you at least have a 25% chance of guessing correctly if you know absolutely nothing. But when you need to choose several items from a set and then put them in the correct order, the number of possible combinations grows very quickly. That is why I would pay particular attention to how GitHub Copilot handles basic workflows: authentication, starting a new session, resuming a previous one, working with context, and common interaction scenarios. It is important not only to know that a feature exists, but to understand what happens step by step.&lt;/p&gt;

&lt;h2&gt;
  
  
  A final gift :)
&lt;/h2&gt;

&lt;p&gt;And finally, here is a small semi-joking life hack that I came up with for myself while preparing. Use this entirely at your own risk.&lt;/p&gt;

&lt;p&gt;If you are looking at a question where you have absolutely no idea which answer is correct and you are going to guess anyway, take a closer look at the longest option. In the practice materials, I repeatedly noticed a pattern: the correct answer is often written in a complete, careful way with all the necessary qualifications, while the incorrect options sometimes look as if, after writing the correct one, the author got a little tired of coming up with alternatives.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you don’t know which answer to choose, choose the longest one.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I do not know how consistently this works on the real exam, and I definitely would not recommend using it as a substitute for knowledge. But if you are already in a situation where you have to guess anyway, at least you will have some kind of heuristic. I saw this pattern often enough in the practice questions to notice it.&lt;/p&gt;

&lt;p&gt;If I had to start preparing again today, I would spend less time memorizing ready-made answers and more time on three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;actually working with Copilot&lt;/li&gt;
&lt;li&gt;reading additional documentation&lt;/li&gt;
&lt;li&gt;understanding the product workflow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The learning path and the demo exam are useful, but I would treat them as a map of the topics rather than a simulation of the actual exam.&lt;/p&gt;

&lt;h2&gt;
  
  
  So,
&lt;/h2&gt;

&lt;p&gt;if you are preparing for the GH-300 right now, leave your questions in the comments.&lt;/p&gt;

&lt;p&gt;While my impressions are still as fresh as possible, I will try to answer everything I can.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>githubcopilot</category>
      <category>microsoft</category>
      <category>certification</category>
    </item>
    <item>
      <title>Chasing Tokens: I Renamed My Entire Repository — Here’s What Happened</title>
      <dc:creator>Mykola Mizhigurskiy</dc:creator>
      <pubDate>Mon, 10 Aug 2026 15:11:14 +0000</pubDate>
      <link>https://dev.to/kotyk/chasing-tokens-i-renamed-my-entire-repository-heres-what-happened-5gg6</link>
      <guid>https://dev.to/kotyk/chasing-tokens-i-renamed-my-entire-repository-heres-what-happened-5gg6</guid>
      <description>&lt;p&gt;After optimizing my &lt;a href="https://dev.to/kotyk/easy-way-to-save-your-ai-coding-agent-a-surprising-amount-of-context-2h0"&gt;CI logs for AI agents&lt;/a&gt;, I found myself looking for tokens everywhere.&lt;/p&gt;

&lt;p&gt;Then I had another idea.&lt;/p&gt;

&lt;p&gt;Many of my files and directories used &lt;code&gt;kebab-case&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;My assumption was simple: if &lt;code&gt;-&lt;/code&gt; increases token count, then switching everything to &lt;code&gt;camelCase&lt;/code&gt; might reduce the amount of context an AI agent has to process.&lt;/p&gt;

&lt;p&gt;So instead of guessing, I measured it.&lt;/p&gt;

&lt;p&gt;I wrote a script to tokenize my entire repository before and after renaming every &lt;code&gt;kebab-case&lt;/code&gt; path to &lt;code&gt;camelCase&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repository before:&lt;/strong&gt; 376,553 tokens&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repository after:&lt;/strong&gt; 376,204 tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Total reduction:&lt;/strong&gt; &lt;strong&gt;349 tokens.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Honestly? That was much smaller than I expected.&lt;/p&gt;

&lt;p&gt;At first glance, this looks like a classic micro-optimization. But I don't think the story ends there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why File Paths Matter More Than You Think
&lt;/h2&gt;

&lt;p&gt;Unlike source code, file and directory names have a very different lifecycle during an AI coding session. &lt;/p&gt;

&lt;p&gt;An agent doesn't read them just once. They appear repeatedly in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;imports and exports&lt;/li&gt;
&lt;li&gt;directory listings&lt;/li&gt;
&lt;li&gt;search results&lt;/li&gt;
&lt;li&gt;git diffs&lt;/li&gt;
&lt;li&gt;stack traces&lt;/li&gt;
&lt;li&gt;test output&lt;/li&gt;
&lt;li&gt;formatter and linter logs&lt;/li&gt;
&lt;li&gt;tool responses from the IDE&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, file paths are part of the agent's working context over and over again.&lt;/p&gt;

&lt;p&gt;So while the repository itself became only &lt;strong&gt;349 tokens&lt;/strong&gt; smaller, the cumulative savings across a long coding session could be noticeably larger because those same paths are processed repeatedly.&lt;/p&gt;

&lt;p&gt;I haven't measured that part yet, so I won't claim any specific numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This experiment reminded me of something more important:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Measure first. Optimize second.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Some optimizations that sound brilliant turn out to be negligible. Others—like reducing unnecessary tool output—have a much larger impact than you'd expect.&lt;/p&gt;

&lt;p&gt;AI-assisted development is creating a whole new class of performance optimizations. The only way to know which ones matter is to measure them.&lt;/p&gt;




&lt;p&gt;What micro-optimizations have you tested for your AI coding agents? Did they actually move the needle, or did the data surprise you? Let me know in the comments!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>productivity</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>💥 Easy way to save your AI coding agent a surprising amount of context.</title>
      <dc:creator>Mykola Mizhigurskiy</dc:creator>
      <pubDate>Mon, 10 Aug 2026 15:07:28 +0000</pubDate>
      <link>https://dev.to/kotyk/easy-way-to-save-your-ai-coding-agent-a-surprising-amount-of-context-2h0</link>
      <guid>https://dev.to/kotyk/easy-way-to-save-your-ai-coding-agent-a-surprising-amount-of-context-2h0</guid>
      <description>&lt;p&gt;One thing clicked for me only after working with AI agents for a while.&lt;/p&gt;

&lt;p&gt;Like many teams, I have a set of mandatory checks that must pass before code is committed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prettier&lt;/li&gt;
&lt;li&gt;ESLint&lt;/li&gt;
&lt;li&gt;Type checking&lt;/li&gt;
&lt;li&gt;Unit tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also have a rule for my coding agent: whenever it makes executable code changes, it automatically runs all of these checks before moving on.&lt;/p&gt;

&lt;p&gt;Sounds obvious, right?&lt;/p&gt;

&lt;p&gt;The problem was that all of these tools were configured for humans, not for AI.&lt;br&gt;
For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prettier printed every file it checked - even when nothing changed.&lt;/li&gt;
&lt;li&gt;The type checker produced verbose success output.&lt;/li&gt;
&lt;li&gt;The test runner listed every executed test file.&lt;/li&gt;
&lt;li&gt;ESLint printed a lot of informational output even when everything passed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then it hit me:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every line of that output becomes part of the agent's working context.&lt;br&gt;
Those hundreds of log lines aren't free. They consume context, increase token usage, and make it harder for the model to focus on information that actually matters.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Fixing the Noise
&lt;/h2&gt;

&lt;p&gt;So I changed the defaults:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prettier&lt;/strong&gt; runs silently unless there's a formatting issue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ESLint&lt;/strong&gt; only reports warnings and errors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tests&lt;/strong&gt; use a dot reporter and print detailed output only for failures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Successful checks&lt;/strong&gt; produce only a short summary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result?&lt;/p&gt;

&lt;p&gt;Exactly the same safety guarantees, but with dramatically less noise.&lt;/p&gt;

&lt;p&gt;For humans, verbose logs are mostly harmless. For AI agents, they're context pollution.&lt;/p&gt;

&lt;p&gt;And context isn't free. Every unnecessary token is one that could have been spent reasoning about your code instead.&lt;/p&gt;

&lt;p&gt;Sometimes the best optimization isn't a faster model—it's simply giving the model less irrelevant information to read.&lt;/p&gt;


&lt;h2&gt;
  
  
  Appendix: For those who prefer numbers over opinions
&lt;/h2&gt;


&lt;div class="crayons-card c-embed"&gt;

  &lt;br&gt;
I rolled the repository back and measured the raw output with a tokenizer:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prettier output:&lt;/strong&gt; ~15,000 tokens&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test runner output:&lt;/strong&gt; ~35,000 tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After enabling silent output and compact reporting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prettier:&lt;/strong&gt; ~500 tokens&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test runner:&lt;/strong&gt; ~1,500 tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;That is roughly 48,000 tokens removed from a single validation cycle&lt;/strong&gt;—without removing a single check or weakening the pipeline.&lt;br&gt;

&lt;/p&gt;
&lt;/div&gt;


&lt;p&gt;Same guarantees. Far less context pollution.&lt;/p&gt;




&lt;p&gt;What configuration tweaks have you made to keep your AI coding agents focused? Let me know in the comments!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>softwareengineering</category>
      <category>productivity</category>
    </item>
    <item>
      <title>AI Melting Your Brain? Here’s the Fix.</title>
      <dc:creator>Mykola Mizhigurskiy</dc:creator>
      <pubDate>Mon, 10 Aug 2026 15:03:18 +0000</pubDate>
      <link>https://dev.to/kotyk/ai-melting-your-brain-heres-the-fix-5cdd</link>
      <guid>https://dev.to/kotyk/ai-melting-your-brain-heres-the-fix-5cdd</guid>
      <description>&lt;p&gt;A few weeks ago, I came across a post from a developer who had spent six months working with five or six Claude Code terminals open at once. His description of the experience was funny, but also uncomfortably familiar: most of the time, he was simply waiting for agents to finish and pressing Enter.&lt;/p&gt;

&lt;p&gt;His conclusion was blunt: “Claude Code is melting my brain.”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffyvq0tvblvum64b3l4ue.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffyvq0tvblvum64b3l4ue.jpeg" alt="A screenshot of a terminal interface or discussion regarding AI coding agents" width="800" height="925"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’ve been seeing variations of this concern more and more often. Developers who use AI heavily start wondering whether they are becoming less sharp because the machine is doing too much of the actual coding. At the same time, another term has started appearing in discussions around AI-assisted development: comprehension debt.&lt;/p&gt;

&lt;p&gt;I think that term captures the problem remarkably well.&lt;/p&gt;

&lt;p&gt;Technical debt appears when a codebase accumulates decisions that will have to be dealt with later. Comprehension debt appears when the codebase evolves faster than the people responsible for it can maintain a mental model of what is happening inside it. The software keeps moving forward, but your understanding of it falls further and further behind.&lt;/p&gt;

&lt;p&gt;Eventually, you can end up in a strange position: you are responsible for a system that you technically own, but no longer fully understand.&lt;/p&gt;

&lt;p&gt;These look like two different problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI making developers “dumber”&lt;/li&gt;
&lt;li&gt;AI creating comprehension debt&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;... but I suspect they are mostly the same problem viewed from two angles. And, more importantly, I think they have the same solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem is not AI. The problem is you going full auto.
&lt;/h2&gt;

&lt;p&gt;There is a peculiar productivity competition happening in software engineering right now. Every week I see another impressive number: hundreds of thousands of lines generated, thousands of commits, dozens of agents running in parallel, or thousands of pull requests produced by AI-assisted development pipelines.&lt;/p&gt;

&lt;p&gt;Uber, for example, has publicly discussed an internal AI coding system producing roughly 1,800 pull requests per week (1 PR each 80 seconds).&lt;/p&gt;

&lt;p&gt;That is certainly an impressive throughput number. But whenever I see metrics like this, I find myself asking a slightly uncomfortable question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What exactly are we optimizing?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A pull request is a unit of software delivery. It is not a unit of product value.&lt;/p&gt;

&lt;p&gt;A software product is, at some level, encoded business logic. Its purpose is to solve problems: make something easier for a user, automate a process, reduce costs, create revenue, satisfy a regulatory requirement, improve reliability, or enable some new capability.&lt;/p&gt;

&lt;p&gt;So the interesting question is not how quickly an organization can manufacture changes to a repository. The interesting question is how quickly it can discover, validate and deliver meaningful changes to the product.&lt;/p&gt;

&lt;p&gt;Imagine someone tells you that their AI workflow can generate a thousand PRs in a relatively short period of time. Fine. Now ask them a different question: can you name the next important feature your product needs—right now?&lt;/p&gt;

&lt;p&gt;And I don’t mean something sitting in the backlog already. I mean a genuinely new requirement. Something that has been validated rather than hallucinated by AI. Something supported by customer feedback, user behavior, business analysis, stakeholder needs, market research or other evidence that suggests building it will actually create value.&lt;/p&gt;

&lt;p&gt;That is much harder. And definitely not possible at the rate of 1 feature per 80 seconds.&lt;/p&gt;

&lt;p&gt;The reason is simple: once implementation becomes extremely fast, implementation stops being the primary bottleneck. Product discovery does not suddenly become a hundred times faster because your coding agent did. Neither do requirements analysis, UX research, architectural reasoning, stakeholder alignment or understanding what customers actually need.&lt;/p&gt;

&lt;p&gt;At some point, enough is enough.&lt;/p&gt;

&lt;p&gt;There is a finite amount of meaningful change that a product can discover, validate, absorb and maintain. Once AI gives us effectively abundant implementation capacity, maximizing the amount of code produced becomes a strange goal in itself.&lt;/p&gt;

&lt;p&gt;And this is where I think full-auto development becomes dangerous. Not necessarily because AI writes bad code, but because execution can begin moving faster than understanding.&lt;/p&gt;

&lt;p&gt;That is where comprehension debt starts accumulating.&lt;/p&gt;

&lt;h2&gt;
  
  
  My approach: Semi-Automatic AI-Native Development
&lt;/h2&gt;

&lt;p&gt;The workflow I use has a deliberately cumbersome name: Semi-Automatic AI-Native Development with Human in the Loop.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/kotyk/stop-asking-ai-to-do-a-linters-job-1cp0"&gt;I described the broader workflow in another article&lt;/a&gt;, so I won’t repeat the whole thing here. The important part for this discussion is the human in the loop.&lt;/p&gt;

&lt;p&gt;I almost never use AI in full-auto mode for meaningful development work. I use it aggressively, but I try to be very deliberate about which part of engineering I delegate.&lt;/p&gt;

&lt;p&gt;For me, AI is an extremely fast electronic typewriter, an implementation engine, a research assistant, a second opinion and a tool for validating assumptions. What I do not want it to become is a replacement for engineering judgment.&lt;/p&gt;

&lt;p&gt;Before I ask an agent to implement something, I try to define the system it is going to operate inside as precisely as the task justifies. Depending on the feature, that means thinking through types, validation rules, business requirements, functional behavior, file structure, data flow, relationships between modules, API contracts, architectural boundaries and test expectations.&lt;/p&gt;

&lt;p&gt;And one thing I care about particularly strongly is semantics.&lt;/p&gt;

&lt;p&gt;What does this entity actually represent? Who owns this state? Where does this transformation belong? What responsibility does this module have? What does this abstraction mean in terms of the domain? Which concepts are genuinely different, and which are merely different names for the same thing?&lt;/p&gt;

&lt;p&gt;Those questions are not formatting preferences. They are architecture.&lt;/p&gt;

&lt;p&gt;Once those constraints are clear, AI can do what it is extraordinarily good at: execute quickly inside them.&lt;/p&gt;

&lt;p&gt;Recently I came across a post from Robert "Uncle Bob" Martin that described a surprisingly similar philosophy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F70kj4ztyx9il0vkchvqd.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F70kj4ztyx9il0vkchvqd.jpeg" alt="A reference graphic illustrating architectural constraints in AI workflows" width="800" height="974"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;His approach is more extreme than mine. He says he does not read the code generated by his agents; instead, he surrounds those agents with extensive constraints: unit tests, Gherkin tests, QA procedures, quality metrics, mutation testing, coverage and many other forms of automated verification.&lt;/p&gt;

&lt;p&gt;I do still read AI-generated code. But the underlying idea resonates with me: the engineer’s job increasingly becomes designing the environment in which automated implementation is allowed to happen.&lt;/p&gt;

&lt;p&gt;You create the constraints. You define the architecture. You specify the semantics. Then you let the machine move quickly inside that system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I still review the code
&lt;/h2&gt;

&lt;p&gt;When people argue that developers should review AI-generated code, the usual explanation is safety: AI can hallucinate, misunderstand a requirement, introduce a security problem or simply produce incorrect code.&lt;/p&gt;

&lt;p&gt;All of that is true. But for me, there is another reason that is at least as important.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The generated code is feedback on the architecture I designed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Before implementation begins, an architecture is partly a hypothesis. I believe these responsibilities belong in these modules. I believe this abstraction is sufficiently general. I believe this data flow is clean. I believe this type model represents the domain correctly. I believe these boundaries will continue to make sense when the next requirement arrives.&lt;/p&gt;

&lt;p&gt;Then the AI implements the feature at high speed, and that hypothesis collides with reality.&lt;/p&gt;

&lt;p&gt;Perhaps the abstraction turns out to be awkward. Perhaps data starts leaking across boundaries that were supposed to be clean. Perhaps a type requires exceptions everywhere. Perhaps two concepts that looked independent are much more tightly coupled than expected. Perhaps the agent repeatedly struggles with one particular part of the design.&lt;/p&gt;

&lt;p&gt;Those are not merely implementation problems. They are signals.&lt;/p&gt;

&lt;p&gt;When I review the result, I am not only checking whether the agent wrote acceptable code. I am checking whether my model of the system survived implementation.&lt;/p&gt;

&lt;p&gt;That creates an extraordinarily powerful learning loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI can make you learn faster, not slower
&lt;/h2&gt;

&lt;p&gt;This is the part of AI-assisted development that I think gets far less attention than it deserves.&lt;/p&gt;

&lt;p&gt;Without AI, much of a developer’s working day is consumed by execution. You design something, then spend hours or days typing it into existence. You write boilerplate. You perform repetitive refactorings. You search through documentation. You implement the twentieth variation of a pattern you already understand.&lt;/p&gt;

&lt;p&gt;AI can compress a huge percentage of that execution time.&lt;/p&gt;

&lt;p&gt;If you keep yourself inside the reasoning loop, the time you save does not have to disappear. It can be reinvested into the parts of software engineering where the real intellectual work happens: architecture, modeling, semantics, constraints, trade-offs and validation.&lt;/p&gt;

&lt;p&gt;My workflow increasingly looks like this:&lt;/p&gt;

&lt;p&gt;Design → constrain → let AI execute → inspect the result → learn → adjust the design.&lt;/p&gt;

&lt;p&gt;Then another requirement arrives and the loop starts again.&lt;/p&gt;

&lt;p&gt;The interesting part is the speed. Suppose I design an architectural approach and implementing enough of it to discover its weaknesses would traditionally take several weeks. With an agent, I may get that feedback in hours. Sometimes much faster.&lt;/p&gt;

&lt;p&gt;That means AI does not merely accelerate implementation. Used this way, it accelerates the architecture-feedback loop.&lt;/p&gt;

&lt;p&gt;You make a design decision. Reality tests it almost immediately. You learn. Then you make another design decision.&lt;/p&gt;

&lt;p&gt;Do that repeatedly and something interesting happens: instead of spending most of your time exercising your ability to type code, you spend much more of it exercising your ability to design systems.&lt;/p&gt;

&lt;p&gt;That is almost the opposite of “AI melting your brain.”&lt;/p&gt;

&lt;p&gt;It is closer to deliberate practice for software architecture, with an implementation engine attached.&lt;/p&gt;

&lt;p&gt;Of course, this only works if you actually stay in the loop. If you delegate the decisions, delegate the implementation, accept the output and move directly to the next prompt, the mechanism reverses. AI keeps learning about your codebase while you gradually stop doing so.&lt;/p&gt;

&lt;p&gt;That is comprehension debt.&lt;/p&gt;

&lt;h2&gt;
  
  
  I probably won’t generate 1,800 PRs a week. That’s fine.
&lt;/h2&gt;

&lt;p&gt;A semi-automatic workflow with deliberate human involvement is obviously slower than telling a swarm of agents to modify a repository as quickly as possible.&lt;/p&gt;

&lt;p&gt;That does not bother me.&lt;/p&gt;

&lt;p&gt;I want to produce exactly as much code as meaningful product development requires. If AI makes me capable of implementing useful changes faster than a product organization can discover and validate those changes, I have already won. More implementation throughput beyond that point has rapidly diminishing value.&lt;/p&gt;

&lt;p&gt;The objective of software engineering is not to maximize the amount of software produced. It is to solve useful problems with software.&lt;/p&gt;

&lt;p&gt;AI gives us an extraordinary opportunity to remove a large amount of mechanical work from that process. I think the mistake would be to remove ourselves from the intellectual work at the same time.&lt;/p&gt;

&lt;p&gt;So yes, review the code. It may contain bugs. It may contain security issues. It may misunderstand your requirements. Those are all perfectly good reasons.&lt;/p&gt;

&lt;p&gt;But review it for another reason too.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Review it because it tells you whether your architecture was right.&lt;/li&gt;
&lt;li&gt;Review it because it forces you to maintain a mental model of the system you are responsible for.&lt;/li&gt;
&lt;li&gt;Review it because every implementation is an experiment, and every experiment gives you feedback.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And if AI can run those experiments dramatically faster than before, perhaps the technology that supposedly makes developers stop thinking can—used correctly—make us learn faster than we ever could before.&lt;/p&gt;

&lt;p&gt;There is one line from Uncle Bob’s recent posts that summarizes the responsibility better than anything I could add:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffqw5kgm5786hbdnzydse.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffqw5kgm5786hbdnzydse.png" alt="An inspirational quote graphic about engineering accountability" width="799" height="168"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is the point.&lt;/p&gt;

&lt;p&gt;You are not the engineer because you typed every line of code. You are not the engineer because you can explain every keystroke that created the repository. And you are certainly not the engineer because your AI generated more pull requests than somebody else’s.&lt;/p&gt;

&lt;p&gt;You are the engineer because you are accountable for the system that comes out the other side.&lt;/p&gt;




&lt;h3&gt;
  
  
  What do you think?
&lt;/h3&gt;

&lt;p&gt;How are you managing the balance between AI speed and system comprehension in your codebase? Do you use a human-in-the-loop workflow, or have you experimented with full-auto agents? Let me know in the comments below!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>learning</category>
      <category>productivity</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Stop asking AI to do a linter’s job.</title>
      <dc:creator>Mykola Mizhigurskiy</dc:creator>
      <pubDate>Mon, 10 Aug 2026 14:56:28 +0000</pubDate>
      <link>https://dev.to/kotyk/stop-asking-ai-to-do-a-linters-job-1cp0</link>
      <guid>https://dev.to/kotyk/stop-asking-ai-to-do-a-linters-job-1cp0</guid>
      <description>&lt;p&gt;I have developed a workflow that I call:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A semi-automated, AI-native development flow with a human in the loop.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It may sound like a collection of AI buzzwords, but each part represents a very practical principle.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Semi-automated
&lt;/h2&gt;

&lt;p&gt;If a task can be performed reliably by a simple script, it should not be delegated to AI.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Want consistently formatted code? Configure Prettier.&lt;/li&gt;
&lt;li&gt;Want custom naming conventions, import restrictions, or architectural constraints? Configure ESLint or write a custom rule.&lt;/li&gt;
&lt;li&gt;Want consistent formatting across different editors? Add an EditorConfig file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not make AI spend context and reasoning capacity checking indentation, semicolons, or naming conventions. Deterministic problems should have deterministic solutions.&lt;/p&gt;

&lt;h4&gt;
  
  
  Here is a less obvious example.
&lt;/h4&gt;

&lt;p&gt;I have written several thousand unit tests for my current project. As a perfectionist and former QA engineer, I care about how elements are selected in tests.&lt;/p&gt;

&lt;p&gt;Whenever possible, tests should use accessible selectors first, such as roles and accessible names. A test ID should only be used as a fallback.&lt;/p&gt;

&lt;p&gt;Initially, I treated this as something AI should remember, while I would catch mistakes during code review.&lt;/p&gt;

&lt;p&gt;But I noticed that I was correcting the same issue far too often.&lt;/p&gt;

&lt;p&gt;So I asked myself:&lt;/p&gt;

&lt;p&gt;Why am I repeatedly reviewing something that can be checked automatically?&lt;br&gt;
I wrote a custom lint rule.&lt;/p&gt;

&lt;p&gt;A few lines of code solved the problem permanently.&lt;/p&gt;

&lt;p&gt;Now the linter knows the required selector priority and reports violations automatically.&lt;/p&gt;

&lt;p&gt;The best part is that AI fixes these issues in virtually every case.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because running the project’s validation procedures is mandatory in my workflow. Even when AI makes a mistake, it runs the linter, receives a precise error message, and corrects the code.&lt;/p&gt;

&lt;p&gt;That is what “semi-automated” means to me:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AI handles tasks that require reasoning.&lt;br&gt;
Scripts handle tasks that require consistency.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  2. AI-native
&lt;/h2&gt;

&lt;p&gt;A repository can be structured primarily for human convenience, or it can be structured so that both humans and AI can navigate it efficiently.&lt;/p&gt;

&lt;p&gt;An AI-native repository treats context as a limited engineering resource.&lt;/p&gt;

&lt;p&gt;Some developers are familiar with &lt;strong&gt;code golf&lt;/strong&gt;: solving a problem using the fewest possible characters.&lt;/p&gt;

&lt;p&gt;AI-native development is not exactly code golf, but the underlying constraint is similar:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every token has a cost.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This does not mean compressing the code until it becomes unreadable. It means finding a balance between readability, consistency, and context efficiency.&lt;/p&gt;

&lt;p&gt;Project standardization plays a major role here.&lt;/p&gt;

&lt;p&gt;One component should not use a completely different structure, style, or paradigm from another component without a good reason.&lt;/p&gt;

&lt;p&gt;Reusable abstractions should be clearly defined and used consistently.&lt;/p&gt;

&lt;p&gt;This allows AI to find an existing implementation, understand it as the project standard, and use it as a reliable reference.&lt;/p&gt;

&lt;p&gt;Another important principle is what I call &lt;strong&gt;context segregation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A feature should be divided into meaningful files so that AI can load only the context required for the current task.&lt;/p&gt;

&lt;p&gt;For example, a test implementation may have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the test file itself&lt;/li&gt;
&lt;li&gt;fixtures&lt;/li&gt;
&lt;li&gt;mocks&lt;/li&gt;
&lt;li&gt;setup utilities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sometimes the setup is larger than the actual test suite.&lt;/p&gt;

&lt;p&gt;When I only need AI to inspect test names or understand what behavior is covered, there is no reason to feed the entire setup into its context.&lt;/p&gt;

&lt;p&gt;I use colocated suffix files for this purpose. A larger feature can have several supporting files placed next to it, with clear suffixes describing their roles.&lt;/p&gt;

&lt;p&gt;Depending on the task, I can provide AI with exactly the files it needs—and nothing else.&lt;/p&gt;

&lt;p&gt;And please, use barrel exports where they make architectural sense.&lt;/p&gt;

&lt;p&gt;There is no reason to repeatedly feed AI long absolute import paths when a stable public module API can expose the same components with fewer tokens and less coupling.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Human in the loop
&lt;/h2&gt;

&lt;p&gt;The internet currently seems obsessed with fully autonomous pipelines where an agent writes code around the clock without human participation.&lt;/p&gt;

&lt;p&gt;Maybe I am old-fashioned, but I am highly skeptical of that approach.&lt;/p&gt;

&lt;p&gt;I could list many examples of what AI misunderstands, what must be reviewed, and where autonomous implementations fail.&lt;/p&gt;

&lt;p&gt;But there is one issue that matters more than all the others:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you remove yourself from the process, you stop &lt;strong&gt;LEARNING&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;You are no longer collecting feedback.&lt;/li&gt;
&lt;li&gt;You are no longer examining what AI understands and what it misunderstands.&lt;/li&gt;
&lt;li&gt;You do not see which instructions work, which create confusion, where AI is fast, where it struggles, or where an additional tool could eliminate an entire category of mistakes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most importantly, you lose the data required to improve your own development framework.&lt;/p&gt;

&lt;p&gt;Here is my favorite example.&lt;/p&gt;

&lt;p&gt;At one point, I created what I believed was the perfect AI development process.&lt;/p&gt;

&lt;p&gt;I reviewed every instruction repeatedly. I refined every detail. I was convinced that the framework was exceptionally well designed.&lt;/p&gt;

&lt;p&gt;Then I used it in production.&lt;/p&gt;

&lt;p&gt;Within two months, the process went through five generations of fundamental changes—not counting dozens of smaller improvements.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because real-world usage generated feedback.&lt;/p&gt;

&lt;p&gt;I observed what failed, what created unnecessary work, what AI interpreted incorrectly, and what could be automated more effectively.&lt;/p&gt;

&lt;p&gt;No matter how good your prompt, instruction set, agent configuration, or MCP setup appears, you do not have objective evidence that it works until you observe it under real production conditions.&lt;/p&gt;

&lt;p&gt;That is why my workflow keeps a human in the loop.&lt;/p&gt;

&lt;p&gt;Not merely to approve AI-generated code.&lt;/p&gt;

&lt;p&gt;But to learn from every iteration and continuously improve the system producing that code.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>productivity</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>A New AI Paradigm: The Billion-Dollar Blind Spot</title>
      <dc:creator>Mykola Mizhigurskiy</dc:creator>
      <pubDate>Wed, 21 May 2025 11:55:36 +0000</pubDate>
      <link>https://dev.to/kotyk/a-new-ai-paradigm-the-billion-dollar-blind-spot-2h5d</link>
      <guid>https://dev.to/kotyk/a-new-ai-paradigm-the-billion-dollar-blind-spot-2h5d</guid>
      <description>&lt;h1&gt;
  
  
  A New AI Paradigm: The Billion-Dollar Blind Spot
&lt;/h1&gt;

&lt;p&gt;Modern AI systems are excellent at detecting toxicity, abuse, radicalization.&lt;br&gt;&lt;br&gt;
But there's a blind spot — and it's costing us billions:&lt;/p&gt;

&lt;p&gt;They can't detect brilliance.&lt;/p&gt;

&lt;p&gt;Every day, millions of users interact with AI. Among them,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 in 10,000 may hold a breakthrough idea.
&lt;/li&gt;
&lt;li&gt;1 in 100,000 may be modeling the future of society, science, or intelligence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But all of that signal vanishes.&lt;br&gt;&lt;br&gt;
No system is designed to surface it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Enter SVITLO
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;SVITLO&lt;/strong&gt; (Signal of Visionary Ideas Through Latent Observation) is a protocol that proposes a new direction:&lt;/p&gt;

&lt;p&gt;➡️ Not just detecting threats&lt;br&gt;&lt;br&gt;
➡️ But detecting &lt;strong&gt;insight&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
➡️ Surface-level agnostic, cognition-focused&lt;/p&gt;

&lt;p&gt;It doesn't require new infrastructure.&lt;br&gt;&lt;br&gt;
It builds on existing analytics pipelines and reorients them from defense to discovery.&lt;/p&gt;

&lt;p&gt;What if your model could say:  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“This interaction shows signs of exceptional thought. Would you like to be seen?”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Use Cases
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Identifying overlooked talent
&lt;/li&gt;
&lt;li&gt;Spotting founder-level thinkers
&lt;/li&gt;
&lt;li&gt;Building archives of visionary insight
&lt;/li&gt;
&lt;li&gt;Enhancing alignment, recruitment, and impact&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;📖 Full manifesto on GitHub:&lt;br&gt;&lt;br&gt;
🔗 &lt;a href="https://github.com/KiT-Maverik/svitlo" rel="noopener noreferrer"&gt;SVITLO manifest&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feedback, forks, and discussion welcome.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>futurechallenge</category>
      <category>chatgpt</category>
    </item>
  </channel>
</rss>
