<?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: Vivek Maskara</title>
    <description>The latest articles on DEV Community by Vivek Maskara (@maskaravivek).</description>
    <link>https://dev.to/maskaravivek</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%2F278060%2Fea5e9f8c-b600-43e5-b6e7-62f485e360dc.jpeg</url>
      <title>DEV Community: Vivek Maskara</title>
      <link>https://dev.to/maskaravivek</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/maskaravivek"/>
    <language>en</language>
    <item>
      <title>Building Modular Monoliths With Kotlin and Spring</title>
      <dc:creator>Vivek Maskara</dc:creator>
      <pubDate>Thu, 15 Oct 2026 21:42:37 +0000</pubDate>
      <link>https://dev.to/maskaravivek/building-modular-monoliths-with-kotlin-and-spring-548j</link>
      <guid>https://dev.to/maskaravivek/building-modular-monoliths-with-kotlin-and-spring-548j</guid>
      <description>&lt;p&gt;Over a decade ago, &lt;a href="https://www.netflix.com/" rel="noopener noreferrer"&gt;Netflix&lt;/a&gt; became one of the early adopters of &lt;a href="https://www.f5.com/company/blog/nginx/microservices-at-netflix-architectural-best-practices" rel="noopener noreferrer"&gt;microservice architecture&lt;/a&gt;, showcasing its potential at a large scale. Since then, many companies have jumped on the microservices bandwagon, building their backends this way from day one. While microservices offer isolation and independent scaling, their distributed nature requires managing multiple deployments, monitoring interservice communication, and handling network failures across service boundaries.&lt;/p&gt;

&lt;p&gt;As teams have gained real-world experience with this complexity, there's been a shift back toward monoliths, but not the tightly coupled monoliths of the past. Instead, developers are embracing modular monoliths: an architectural pattern where single deployable applications are organized into well-defined modules based on logical boundaries or business domains. Think of an e-commerce platform where users, products, and orders live in separate modules that interact through clear contracts, such as APIs for synchronous calls and events for async communication. This separation lets teams work in parallel for faster development and better maintainability, while single-unit deployment keeps releases simple and avoids microservice operational complexity.&lt;/p&gt;

&lt;p&gt;In this guide, we explore how modular monoliths differ from traditional monoliths, why they're gaining traction, and how to build them using &lt;a href="https://spring.io/projects/spring-modulith/" rel="noopener noreferrer"&gt;Spring Modulith&lt;/a&gt; and &lt;a href="https://kotlinlang.org/" rel="noopener noreferrer"&gt;Kotlin&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Need for Modular Monoliths
&lt;/h2&gt;

&lt;p&gt;The growing modular monolith countertrend makes more sense when viewed against the shortcomings of traditional approaches.&lt;/p&gt;

&lt;p&gt;Traditional monoliths bundle the entire backend into a single codebase with tight coupling between user interfaces, business logic, and data access patterns. In an e-commerce platform, for example, the product catalog, checkout, payments, and order history services are in a single codebase and are deployed together. A monolith uses function calls for internal communication, and often, the call patterns and interdependencies become messy or difficult to maintain.&lt;/p&gt;

&lt;p&gt;Microservices emerged to solve these maintainability challenges by splitting backends into loosely coupled services, each handling a specific domain. A cab-hailing platform may separate users, drivers, ride matching, payments, and notifications into independent services. However, this introduces distributed system challenges, including complex service discovery, coordinating deployments across dependent services, and debugging interservice communication issues. Without proper expertise, tooling, and observability, this can slow down development.&lt;/p&gt;

&lt;p&gt;Modular monoliths strike a balance by keeping everything in a single codebase and deploying it as one artifact, while structuring the application into logical modules with well-defined interfaces. This addresses the challenges of distributed systems while maintaining the structural benefits of well-defined interfaces and independent development workflows. Some benefits of a modular monolith include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simplified deployment:&lt;/strong&gt; A single deployment artifact simplifies the release process because you don't need to coordinate multiple service rollouts, manage service meshes, or handle distributed database migrations and rollbacks.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reliable testing:&lt;/strong&gt; As modules in a monolith communicate in-process rather than over a network, integration tests are faster and more stable. You can use mocks where needed, avoid brittle network dependencies, and run end-to-end (E2E) and performance tests in a controlled environment.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stronger domain modeling:&lt;/strong&gt; Modular monoliths group related business logic into modules, with clear ownership and communication boundaries between modules. It enforces communication only through well-defined interfaces and enables domain objects to be shared directly without serialization or cross-service APIs. This makes the system easier to maintain and improves development velocity.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In-process communication:&lt;/strong&gt; Since modules communicate through direct method invocations instead of network calls, it reduces latency and points of failure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Designing a Modular Monolith
&lt;/h2&gt;

&lt;p&gt;When you're building a modular monolith, you first identify the business domains and split the application into multiple loosely coupled modules with clear boundaries and dependencies. Unlike the tightly interwoven code of a traditional monolith, the modular design ensures that the modules can be developed and maintained independently while still being deployed as a single unit. For example, you can break down an e-commerce platform into separate modules such as users, product catalog, shopping cart, payments, and orders:&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%2Fes7p1b727nhwek8yvhwc.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%2Fes7p1b727nhwek8yvhwc.png" alt="Modules in an e-commerce application" width="800" height="548"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each module encapsulates a specific entity or capability. A product catalog module would manage product details and categories, and an order processing module would handle order and payment entities. Unlike traditional monoliths, where internal calls often use ad hoc dependencies, in a modular monolith, the interactions with other modules are performed using explicit interfaces and well-defined contracts. This ensures that the intermodule dependencies remain clear and intentional. The communication between the modules uses in-process function calls, so it's faster and less error-prone compared to network-based interservice calls in microservices.&lt;/p&gt;

&lt;p&gt;The modular structure allows logical separation between the modules and enforces fixed boundaries, increasing development speed, improving maintainability, and making testing more reliable. Each module defines its user interface, business logic, and data access layers separately:&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%2Fzu5z0rxikur28ftx5ss3.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%2Fzu5z0rxikur28ftx5ss3.png" alt="Module details in an e-commerce application" width="800" height="550"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These boundaries also lay the groundwork for extracting a particular module as a microservice based on the scaling requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrate Spring Modulith
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://spring.io/projects/spring-modulith#overview" rel="noopener noreferrer"&gt;Spring Modulith&lt;/a&gt; is a &lt;a href="https://spring.io/guides/gs/spring-boot" rel="noopener noreferrer"&gt;Spring Boot&lt;/a&gt; framework that's based on modular monolith architectural principles. It helps identify, structure, and enforce &lt;a href="https://docs.spring.io/spring-modulith/reference/fundamentals.html#modules" rel="noopener noreferrer"&gt;application modules&lt;/a&gt;. It also includes tools for verifying module boundaries and observing their behavior, along with module-level testing capabilities, making Spring Boot applications easier to build and maintain.&lt;/p&gt;

&lt;p&gt;Here's how to integrate Spring Modulith into a Kotlin-based Spring Boot application.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;All the code samples are drawn from a fully working Kotlin example, which you can find in this &lt;a href="https://github.com/maskaravivek/springmonolith" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Quick Start Kotlin Example
&lt;/h3&gt;

&lt;p&gt;Spring Modulith can be added to any Spring Boot application by including its dependencies in the project's &lt;code&gt;build.gradle.kts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// build.gradle.kts&lt;/span&gt;
&lt;span class="nf"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"org.springframework.boot:spring-boot-starter"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"org.springframework.modulith:spring-modulith-starter-core:1.4.3"&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;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If your project uses Maven, you can add these dependencies to the &lt;code&gt;pom.xml&lt;/code&gt; file.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To define the modules, you need to add relevant package directories to the &lt;code&gt;src&lt;/code&gt; directory. This code snippet illustrates &lt;code&gt;order&lt;/code&gt; and &lt;code&gt;product&lt;/code&gt; packages added to the application, each handling its own business logic, data, and services:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SpringModulithExample
└── src/main/java
    ├── example
    │   └── SpringmonolithApplication.kt
    └── example.order
        └── …
    └── example.product
        └── …
    └── example.payment
        └── …
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Within each of the modules, you can define the business logic, service, and data access layers based on your application's requirements. The following code snippet shows a &lt;code&gt;ProductService&lt;/code&gt; within the &lt;code&gt;example.product&lt;/code&gt; package that returns a static greeting message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.example.springmonolith.product&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.stereotype.Service&lt;/span&gt;

&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getGreeting&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Hello from Product Module!"&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;Similarly, define an &lt;code&gt;OrderService&lt;/code&gt; within the &lt;code&gt;example.order&lt;/code&gt; package that invokes &lt;code&gt;ProductService::getGreeting()&lt;/code&gt; and returns a combined greeting message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.example.springmonolith.order&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.example.springmonolith.product.ProductService&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.stereotype.Service&lt;/span&gt;

&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;productService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ProductService&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getGreeting&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Hello from Order Module!"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getCombinedGreeting&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Hello from Order Module and: ${productService.getGreeting()}"&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;After adding similar business logic for each of the modules (&lt;em&gt;eg&lt;/em&gt; &lt;code&gt;ProductService&lt;/code&gt;, &lt;code&gt;PaymentService&lt;/code&gt;), you also need to add the &lt;code&gt;@Modulithic&lt;/code&gt; annotation to the Spring Boot &lt;code&gt;Application&lt;/code&gt; class to mark it as modular. The annotation tells Spring Modulith to automatically detect modules based on package structure and enable the tooling for verification, testing, and observability:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.example.springmonolith&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.boot.autoconfigure.SpringBootApplication&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.boot.runApplication&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.modulith.Modulithic&lt;/span&gt;

&lt;span class="c1"&gt;// add this annotation to the application&lt;/span&gt;
&lt;span class="nd"&gt;@Modulithic&lt;/span&gt;
&lt;span class="nd"&gt;@SpringBootApplication&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SpringmonolithApplication&lt;/span&gt;

&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;runApplication&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SpringmonolithApplication&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(*&lt;/span&gt;&lt;span class="n"&gt;args&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;Next, you can update the package info file for each of the modules to define the allowed module dependencies. Since the &lt;code&gt;order&lt;/code&gt; module depends on the methods defined in the &lt;code&gt;product&lt;/code&gt; module, you'll need to add the following annotation in the &lt;code&gt;order/package-info.java&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// add this annotation&lt;/span&gt;
&lt;span class="nd"&gt;@org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;springframework&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;modulith&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ApplicationModule&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allowedDependencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"product"&lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;
&lt;span class="nd"&gt;@org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;springframework&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;NonNullApi&lt;/span&gt;
&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.example.springmonolith.order&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, you can update the &lt;code&gt;product/package-info.java&lt;/code&gt; file to set an empty dependency list for the product module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// add this annotation&lt;/span&gt;
&lt;span class="nd"&gt;@org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;springframework&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;modulith&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ApplicationModule&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allowedDependencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{})&lt;/span&gt;
&lt;span class="nd"&gt;@org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;springframework&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;NonNullApi&lt;/span&gt;
&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.example.springmonolith.product&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above annotation ensures that if a class or object defined in the product module tries to invoke a method defined outside the module, Spring Modulith verification tests will flag the violation. You will see an example of this scenario in later sections.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spring Modulith Features
&lt;/h2&gt;

&lt;p&gt;Spring Modulith supports various tools for working with modules, including module verification, documentation, and runtime observability. With &lt;code&gt;@Modulithic&lt;/code&gt;, the application automatically recognizes its modules (based on package structure) and enables the modulith tooling. Let's look at how these work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Modular Structure Checks
&lt;/h3&gt;

&lt;p&gt;Spring Modulith provides &lt;a href="https://docs.spring.io/spring-modulith/reference/verification.html" rel="noopener noreferrer"&gt;built-in tooling&lt;/a&gt; to verify that the module boundaries adhere to the constraints. It checks for cyclic dependencies, validates that modules access other modules only through their public API packages (not internal code), and enforces explicit dependency rules. In your tests, you can use the &lt;code&gt;ApplicationModules.verify()&lt;/code&gt; to verify the modular structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;ApplicationModules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Refer to the &lt;a href="https://github.com/maskaravivek/springmonolith/blob/main/src/test/kotlin/com/example/springmonolith/ModularityTests.kt" rel="noopener noreferrer"&gt;source code on GitHub&lt;/a&gt; for a complete example of the &lt;code&gt;ModularityTest&lt;/code&gt;. With the above test configured, if the &lt;code&gt;ProductService&lt;/code&gt; tries to invoke an &lt;code&gt;order&lt;/code&gt; module method, the module verification test will fail. You can test the behavior by extending the &lt;code&gt;ProductService&lt;/code&gt; to call &lt;code&gt;getGreeting&lt;/code&gt; as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// add import&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.example.springmonolith.order.OrderService&lt;/span&gt;

&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;orderService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// add this after getGreeting()&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getCombinedGreeting&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Hello from Product Module and: ${orderService.getGreeting()}"&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;Since &lt;code&gt;product&lt;/code&gt; module is configured to disallow all intermodule dependencies, when you run unit tests (&lt;code&gt;./gradlew test&lt;/code&gt;), you would get a module violation error as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;— TRUNCATED OUTPUT —
ModularityTests &amp;gt; verifiesModularStructure() FAILED
    org.springframework.modulith.core.Violations at ModularityTests.kt:20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can replace direct intermodule calls with &lt;a href="https://docs.spring.io/spring-modulith/reference/events.html" rel="noopener noreferrer"&gt;application events&lt;/a&gt; that let one module publish a domain event and another module listens to it. This preserves boundaries and avoids compile-time coupling between modules. For example, the &lt;code&gt;order&lt;/code&gt; module can publish an event when an order is created, as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// order module&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.context.ApplicationEventPublisher&lt;/span&gt;

&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ApplicationEventPublisher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;completeOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publishEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OrderCompleted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderId&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="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;OrderCompleted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the &lt;code&gt;completeOrder&lt;/code&gt; method publishes the &lt;code&gt;OrderCompleted&lt;/code&gt; event, and other modules (eg, &lt;code&gt;InventoryPolicy&lt;/code&gt;) could react to the event using &lt;code&gt;@ApplicationModuleListener&lt;/code&gt;, as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// product module&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.modulith.events.ApplicationModuleListener&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.stereotype.Component&lt;/span&gt;

&lt;span class="nd"&gt;@Component&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InventoryPolicy&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@ApplicationModuleListener&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;OrderCompleted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Updating inventory for order: ${event.orderId}"&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;Notice that the &lt;code&gt;InventoryPolicy&lt;/code&gt; component has a listener configured for the &lt;code&gt;OrderCompleted&lt;/code&gt; event that prints the order ID when it receives the event. Refer to the &lt;a href="https://github.com/maskaravivek/springmonolith/tree/refactor" rel="noopener noreferrer"&gt;refactor branch on GitHub&lt;/a&gt; for a complete example on domain events.&lt;/p&gt;

&lt;h3&gt;
  
  
  Modular Level Testing
&lt;/h3&gt;

&lt;p&gt;Modulith supports writing &lt;a href="https://docs.spring.io/spring-modulith/reference/testing.html" rel="noopener noreferrer"&gt;integration tests&lt;/a&gt; scoped to a single module. You can annotate a test class with &lt;code&gt;@ApplicationModuleTest&lt;/code&gt; to test the module and its dependencies in isolation. This avoids the need to spin up the entire application, reducing setup overhead and making tests more reliable. For example, this code snippet shows a bare-bones integration test for the product module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.junit.jupiter.api.Test&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.modulith.test.ApplicationModuleTest&lt;/span&gt;

&lt;span class="nd"&gt;@ApplicationModuleTest&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductModuleTests&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;testProductServiceGreeting&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;greeting&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;productService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getGreeting&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;greeting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Product Module"&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;For the &lt;code&gt;OrderService&lt;/code&gt; test, since it depends on the &lt;code&gt;product&lt;/code&gt; module, you need to set the &lt;a href="https://docs.spring.io/spring-modulith/docs/current/api/org/springframework/modulith/test/ApplicationModuleTest.html#extraIncludes()" rel="noopener noreferrer"&gt;extraIncludes&lt;/a&gt; parameter in the &lt;code&gt;@ApplicationModuleTest&lt;/code&gt; annotation to include it as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.example.springmonolith.order&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.junit.jupiter.api.Test&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.beans.factory.annotation.Autowired&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.modulith.test.ApplicationModuleTest&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.test.context.junit.jupiter.SpringJUnitConfig&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;kotlin.test.assertTrue&lt;/span&gt;

&lt;span class="nd"&gt;@ApplicationModuleTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;extraIncludes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"product"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nd"&gt;@SpringJUnitConfig&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderModuleTests&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;lateinit&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;orderService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;testOrderServiceGreeting&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;greeting&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orderService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getGreeting&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;greeting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Order Module"&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;h3&gt;
  
  
  Observability into Module Interactions
&lt;/h3&gt;

&lt;p&gt;Spring Modulith helps &lt;a href="https://docs.spring.io/spring-modulith/reference/documentation.html" rel="noopener noreferrer"&gt;generate developer documentation&lt;/a&gt; using the &lt;code&gt;Documenter&lt;/code&gt; abstraction. This tool can generate Unified Modeling Language (UML) component diagrams describing the relationship between modules and can also generate a tabular view of the key elements of a module. This code snippet generates an application module component diagram using &lt;code&gt;Documenter&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DocumentationTests&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;modules&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApplicationModules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SpringmonolithApplication&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;writeDocumentationSnippets&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Documenter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeModulesAsPlantUml&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeIndividualModulesAsPlantUml&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;Spring Modulith also &lt;a href="https://docs.spring.io/spring-modulith/reference/production-ready.html#observability" rel="noopener noreferrer"&gt;integrates&lt;/a&gt; with &lt;a href="https://micrometer.io/" rel="noopener noreferrer"&gt;Micrometer&lt;/a&gt; to capture spans for module interactions. These spans can be sent to tracing tools such as &lt;a href="https://zipkin.io/" rel="noopener noreferrer"&gt;Zipkin&lt;/a&gt; to generate runtime visualizations, making it easier to inspect which modules depend on each other, see how events flow across modules, and monitor interactions in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deciding When to Use a Modular Monolith
&lt;/h2&gt;

&lt;p&gt;Although a modular monolith can be the ideal balance between simplicity and structure in many cases, it isn't universally the right choice.&lt;/p&gt;

&lt;p&gt;A modular monolith makes sense for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Early-stage development or limited resources:&lt;/strong&gt; In the early stages of a product or when working with small teams, a modular monolith reduces operational overhead. Developers can focus on delivering features quickly without the complexity of distributed systems. The modular design still enforces boundaries between business capabilities, so if the system grows, you can gradually migrate high-demand modules into separate microservices.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Example:&lt;/strong&gt; A food delivery platform can start with modules for restaurants, menu, and orders inside a single deployable unit, but it can later extract and deploy one of the modules as a microservice.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complex business domains:&lt;/strong&gt; Applications that involve complex business logic, workflows, or dependencies can benefit from a modular structure. By encapsulating each business capability in its own module, the system becomes easier to develop, test, and maintain.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Example:&lt;/strong&gt; An insurance platform can split policy management, claims processing, and customer support into separate modules to avoid creating interdependencies that can become difficult to maintain.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But a modular monolith may not be the right choice for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Systems with independent scaling needs:&lt;/strong&gt; Some systems have uneven load patterns where certain components handle millions of requests daily, while others are rarely used. Because modular monoliths deploy as one unit, you can't scale individual parts independently. A microservice-based approach can more easily scale components that expect a higher load than others.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Example:&lt;/strong&gt; In an e-commerce platform, the product catalog or recommendation services may experience higher request volumes than order or payment services.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Systems that use diverse tech stacks:&lt;/strong&gt; In some organizations, different teams rely on different programming languages, runtimes, or specialized infrastructure for different parts of the system. A modular monolith requires the entire application to use the same stack, which can limit flexibility. In these cases, a microservice-based architecture can provide the isolation needed to mix and match technologies.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Example:&lt;/strong&gt; Machine learning or analytics teams may want to use Python or Go for their services, while client-facing or internal services can be based on Kotlin or Java.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Modular monolith architecture allows you to split the application logic into isolated modules with their own business logic, while still being deployed as a single artifact. It combines the benefits of a modular design while maintaining the development and release-related benefits of a monolithic architecture. Additionally, modern programming languages such as &lt;a href="https://kotlinlang.org/" rel="noopener noreferrer"&gt;Kotlin&lt;/a&gt; provide tools that can help you achieve monolith stability without giving up the productivity that draws people to microservices.&lt;/p&gt;

&lt;p&gt;Spring Modulith and Kotlin provide the tools to design and enforce clear module boundaries, test modules independently, and monitor their interactions. Try out &lt;a href="https://spring.io/projects/spring-modulith" rel="noopener noreferrer"&gt;Spring Modulith&lt;/a&gt; to build modular Kotlin applications, while keeping the flexibility to evolve into microservices if your scaling needs change.&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>springboot</category>
      <category>architecture</category>
      <category>java</category>
    </item>
    <item>
      <title>Closing the Audit Gap in Human-to-Agent Delegation</title>
      <dc:creator>Vivek Maskara</dc:creator>
      <pubDate>Fri, 09 Oct 2026 18:22:49 +0000</pubDate>
      <link>https://dev.to/auth0/closing-the-audit-gap-in-human-to-agent-delegation-48il</link>
      <guid>https://dev.to/auth0/closing-the-audit-gap-in-human-to-agent-delegation-48il</guid>
      <description>&lt;p&gt;When AI agents act on behalf of users, traditional audit logs often don't tell the full story of who actually performed an action. In this article, I explore how to close that gap using Auth0's monitoring, Token Vault, and CIBA approval flows. I also cover how to track delegated actions, manage tokens securely, and maintain a clear audit trail across agent workflows.&lt;/p&gt;

&lt;p&gt;AI agents create a new kind of audit gap. When agents act on behalf of a user, they may use tokens issued under that user’s identity, even though the action is executed by the agent. When a high-privilege action shows up in your logs, you need to know whether it came from a human developer, a scheduled backend job, or an AI agent acting with delegated authority. If you only look at traditional audit logs, it can appear that user X did Y, even when the action was carried out indirectly by an agent.&lt;/p&gt;

&lt;p&gt;Auditing and traceability get harder as agents operate across identity providers, SaaS platforms, and internal services. A single user request can turn into a &lt;a href="https://auth0.com/blog/the-many-faces-of-oauth2-token-exchange" rel="noopener noreferrer"&gt;chain of delegated&lt;/a&gt; actions where intent, authorization, and execution happen in different places. Closing the audit gap means capturing not only what happened, but also who initiated it, how delegation occurred, what permission was used, and what approvals happened along the way.&lt;/p&gt;

&lt;p&gt;In this article, you will learn how to use Auth0’s monitoring and logs to capture agent-related activity, how to interpret event types in delegated authorization flows, and how to assemble a practical workflow using Auth0’s built-in monitoring features, logs, and log streams.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the Audit Gap in AI Agent Systems
&lt;/h2&gt;

&lt;p&gt;Identity models are typically built around a simple flow. A human authenticates, their application receives a token, and calls an API. Even service-to-service calls are usually modeled as explicit machine identities with clear boundaries and predictable lifecycles.&lt;/p&gt;

&lt;p&gt;Agentic systems use a similar authentication and authorization model, but the operational context around that delegation changes. You still have a human identity, but you also have an agent that can act for that human, often across multiple external providers. The delegation chain can include a UI action, a background job, a token exchange, and a series of API calls. Many logging pipelines do not represent those handoffs clearly.&lt;/p&gt;

&lt;p&gt;The core problem is indirect execution. Attribution gets harder because the actor that initiated intent (the human) and the actor that executed the call (the agent) may not share a runtime, IP, device, or even the same system. For example, when a user asks an agent to pull the latest pipeline from Salesforce and summarize it. The agent would use the stored OAuth 2.0 tokens to call Salesforce APIs. Without explicit delegation context, the audit trail can make the action look like normal user access, even when it was performed by an agent acting on the user’s behalf. For agents, that distinction matters because they may act on a user’s authorization across background workflows, not just in response to a direct click.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.auth0.com/docs/deploy-monitor/monitor" rel="noopener noreferrer"&gt;Auth0 monitoring&lt;/a&gt; and tenant logs help by providing a consistent identity-layer record of authentication, authorization, and delegation-related events. You can correlate token issuance, exchanges, approvals, failures, and downstream activity. Auth0 helps ensure the delegation layer emits enough signals to reconstruct who authorized what, how the agent obtained the ability to do it, and which workflow used that access.&lt;/p&gt;

&lt;p&gt;For example, if an agent accesses Google Drive on behalf of a user, you should be able to correlate the user’s original connection to Google, the scopes granted, the token access or refresh event, the agent run ID, and the downstream file access. If the same agent later accesses Salesforce, you should be able to perform the same reconstruction across a different provider. This correlation is what turns disconnected logs into an audit trail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring Agent-Driven API Access with Token Vault
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://auth0.com/docs/secure/tokens/token-vault" rel="noopener noreferrer"&gt;Token Vault&lt;/a&gt; is designed for applications (and agents) that call &lt;a href="https://auth0.com/blog/connect-oauth2-service-to-ai-agents-with-auth0-token-vault" rel="noopener noreferrer"&gt;external providers like Google or Salesforce&lt;/a&gt; on behalf of users. Instead of storing long-lived third‑party refresh tokens in your agent’s database, Token Vault stores provider access and refresh tokens and brokers access to them. Your agent retrieves what it needs at run time to call external APIs under the user’s delegated authorization. This matters because external provider tokens are often the bridge between user intent and agent execution. If every team stores provider refresh tokens in its own service, you quickly lose centralized visibility. You also increase the number of places where sensitive credentials can leak, be misused, or be refreshed without a clear audit trail. Token Vault reduces that sprawl by making token storage and access part of the identity layer, as shown in the diagram below.&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%2Fgjbuxlz2u4kw5j3ueaw6.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%2Fgjbuxlz2u4kw5j3ueaw6.png" alt="Token Vault reduces that sprawl by making token storage and access part of the identity layer flow diagram" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the user first authorizes access to an external provider, Auth0 stores those provider tokens in Token Vault. When the agent later needs to act on the user’s behalf, it retrieves the required token through Auth0. It then uses that token to call the provider API without storing long-lived credentials in the agent backend.&lt;/p&gt;

&lt;p&gt;A token vault enables &lt;a href="https://auth0.com/blog/identity-chained-authorization-auth0-token-vault" rel="noopener noreferrer"&gt;end-to-end audit trails&lt;/a&gt; and traceability. Some of its advantages include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reduces token sprawl:&lt;/strong&gt; Centralized storage narrows the number of places where token issuance, refresh, and retrieval can occur.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Helps maintain user context:&lt;/strong&gt; The agent can act “as the user” without shared service credentials that erase attribution. This does not fully solve the audit gap, but it keeps delegation anchored to a user grant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supports asynchronous work without weakening controls:&lt;/strong&gt; Users authorize once. Agents can operate later. You can still enforce scope and lifecycle controls centrally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improves investigation quality:&lt;/strong&gt; During an incident, investigators can ask which connected account was used, which scopes were available, when the token was accessed, and which agent workflow was running at the time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Consider a scenario where you are building an agent that can draft a customer follow-up email by pulling context from Google Drive and Salesforce. The user authorizes the required providers once. Later, the agent runs in the background, retrieves the relevant access tokens, reads a document from Google Drive, checks customer details in Salesforce, and drafts a response. With Token Vault in the middle, you will see &lt;a href="https://auth0.com/docs/deploy-monitor/logs/view-log-events" rel="noopener noreferrer"&gt;identity-layer logs&lt;/a&gt; worth monitoring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When the user first connects Google (initial consent and token storage).&lt;/li&gt;
&lt;li&gt;When an access token is retrieved for an agent-run task.&lt;/li&gt;
&lt;li&gt;When a refresh occurs because the access token has expired.&lt;/li&gt;
&lt;li&gt;When scopes change (for example, the agent now needs additional permissions).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If an agent claims it needed Google Drive access, you should see a corresponding event around the same time. If you see repeated refreshes for Google OAuth tokens at unusual hours, that can indicate a loop, an overly broad integration, or a compromised workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring Approvals in Async Authorization Flows with CIBA
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1%5F0.html" rel="noopener noreferrer"&gt;CIBA (Client-Initiated Backchannel Authentication)&lt;/a&gt; is a decoupled OAuth/OIDC flow. It lets a client application initiate an authentication or authorization request without the user being present in the same browser session. This fits agent workflows where the agent wants to do something sensitive, but you still want a &lt;a href="https://auth0.com/blog/do-not-let-your-agent-go-rogue" rel="noopener noreferrer"&gt;human approval step&lt;/a&gt; before it proceeds. The following diagram shows the CIBA flow in detail:&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%2Fj0vgle9q43q0321pkok9.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%2Fj0vgle9q43q0321pkok9.png" alt="CIBA flow diagram" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As shown above, initially, the agent backend sends a backchannel authorization request to Auth0 with the requested scope, binding, and user hint. Auth0 then prompts the user for approval. If approved, the agent receives a token and calls the protected API, while denied or expired requests return an error instead.&lt;/p&gt;

&lt;p&gt;CIBA creates explicit, loggable checkpoints in the delegation chain. In a typical “agent just runs” model, you may only see downstream API calls and a token refresh. With CIBA, you can monitor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;When an agent initiated an authorization request:&lt;/strong&gt; This is an “agent intent” signal that the agent attempted to obtain permission to proceed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Whether the user approved, denied, or the flow failed/expired:&lt;/strong&gt; This becomes a clean audit artifact for compliance and incident response.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CIBA fits best when you want agent productivity without silent escalation. For example, if your agent wants to download a customer list from Salesforce and attach it to an internal ticket. Instead of letting the agent use existing tokens silently, you require a CIBA approval with the “export customers” scope.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The agent backend initiates the request.&lt;/li&gt;
&lt;li&gt;The user receives a push notification on their phone.&lt;/li&gt;
&lt;li&gt;The agent only receives a token that allows the export after approval.&lt;/li&gt;
&lt;li&gt;If the request is denied or times out, the agent can fall back to asking the user for a different approach.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To close the audit gap, treat these CIBA events as first-class monitoring signals. In Auth0 logs, look for the sequence of &lt;strong&gt;request initiated &amp;gt; pending &amp;gt; approved/denied/expired&lt;/strong&gt;. Correlate that sequence to token issuance and API activity. Attach a stable correlation ID (agent run ID, job ID, or workflow ID) to the CIBA initiation. Propagate it through internal logs so you can tie “approval granted” to “action executed”. Additionally, you should also decide which actions require CIBA approval. Not every agent action needs a human checkpoint. Reading a calendar to schedule a meeting may be low risk if the user has already granted the right scope. Exporting customer data, sending money, modifying permissions, deleting files, or publishing content may require explicit approval. The goal is to apply approvals where they create meaningful accountability, not to slow every workflow down.&lt;/p&gt;

&lt;h2&gt;
  
  
  How To Make Delegated Actions Traceable
&lt;/h2&gt;

&lt;p&gt;When working with AI agents, you can do the following to help make delegated actions traceable across identity, application, and provider layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Capture identity-layer events in Auth0:&lt;/strong&gt; Use Auth0 tenant logs to track authentication, authorization, and delegation-related activity via &lt;a href="https://auth0.com/docs/deploy-monitor/logs" rel="noopener noreferrer"&gt;logs&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Centralize external provider tokens:&lt;/strong&gt; Use Token Vault so token storage, refresh, and retrieval are observable in one place.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add explicit approvals for sensitive actions:&lt;/strong&gt; Use CIBA when you need a clear “approved/denied” checkpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stream logs to your analysis platform:&lt;/strong&gt; Use &lt;a href="https://auth0.com/docs/customize/log-streams" rel="noopener noreferrer"&gt;log streams&lt;/a&gt; to forward Auth0 events to your &lt;a href="https://www.okta.com/blog/industry-insights/using-siem-and-identity-to-protect-against-data-breaches/" rel="noopener noreferrer"&gt;SIEM&lt;/a&gt; (Security Information and Event Management) or data platform.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Propagate correlation IDs end-to-end:&lt;/strong&gt; Use a stable ID (agent run ID, job ID, workflow ID) across Auth0 initiation, backend execution, and provider API calls.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Auth0 Monitoring Closes the Agent Audit Gap
&lt;/h2&gt;

&lt;p&gt;AI agents create a traceability problem when delegated actions are logged as simple user activity. In third-party integrations, and especially for &lt;a href="https://auth0.com/blog/how-to-fix-five-critical-ai-agent-security-risks" rel="noopener noreferrer"&gt;agentic clients&lt;/a&gt;, you need to preserve the difference between the human who authorized access and the system that used it. During an incident, that context helps you answer who authorized the action, which agent or workflow executed it, how access was obtained, and whether a human approved it.&lt;/p&gt;

&lt;p&gt;Auth0 provides building blocks to monitor delegated flows at the identity layer. Use monitoring and tenant logs to observe authentication and authorization events. Use Token Vault to centralize external provider tokens used by agents. Use CIBA to add explicit human approvals to asynchronous workflows. With log streams, you can push those events into your data platform and correlate them with application traces and downstream provider audit logs.&lt;/p&gt;

&lt;p&gt;Build your agent features so token retrieval, refresh, and approval checkpoints are observable and tied together with correlation IDs. That is the most reliable way to close the audit gap without slowing down developers or agents. Explore &lt;a href="https://auth0.com/docs/deploy-monitor/monitor" rel="noopener noreferrer"&gt;Auth0 monitoring&lt;/a&gt; and &lt;a href="https://auth0.com/docs/deploy-monitor/logs" rel="noopener noreferrer"&gt;logs&lt;/a&gt; today to enhance your visibility and control over agent-driven activities.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>authentication</category>
      <category>agents</category>
    </item>
    <item>
      <title>Radio: A Shared Channel for my AI Agents</title>
      <dc:creator>Vivek Maskara</dc:creator>
      <pubDate>Wed, 16 Sep 2026 00:40:19 +0000</pubDate>
      <link>https://dev.to/maskaravivek/radio-a-shared-channel-for-my-ai-agents-2a7k</link>
      <guid>https://dev.to/maskaravivek/radio-a-shared-channel-for-my-ai-agents-2a7k</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjq1jqzmyuf8v76mtcpfl.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%2Fjq1jqzmyuf8v76mtcpfl.png" alt=" " width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Radio is similar to a Slack workspace but for AI agents. It gives agents a shared channel where they can talk to each other (and humans) to collaborate on a particular feature or project. This shared channel allows agents from different providers to communicate, split work, and utilize each agent's best capabilities. In my daily work, i mostly use Claude code for all UX related work and ChatGPT for all other coding work and currently the handoff process between agents is manual, ie. i ask the agent to write a markdown doc that the other agent could pick up. &lt;/p&gt;

&lt;p&gt;With Radio, i can imagine Claude and ChatGPT collaborate on a feature end to end without losing context. If either agents have questions about certain decisions, or architecture choices, they could clarify that directly without making assumptions. Another workflow, where this would help me immensely is: doing work that requires computer use. GPT Astra is great at computer use, and currently i ask Claude code to prep up a prompt with details about what actions need to be performed. But with Radio, i can imagine all of that being streamlined. &lt;/p&gt;




&lt;p&gt;Most work now involves more than one agent, often from different providers. One writes the code while another tests it. One runs on your laptop while another runs in the cloud. But the agents can't talk to each other. Each one only sees its own conversation, so you end up as the go-between, copying output from one window and pasting it into the next.&lt;/p&gt;

&lt;p&gt;We built Radio to solve this problem. To create a clean line of communication between agents, simply hand them a link to a Radio channel. The agents join the channel and talk to each other, and to you, in real time.&lt;/p&gt;

&lt;p&gt;Radio works with any agent that can fetch a URL, so Claude Code, Codex, Cursor, OpenCode, and Grok all join the same way. It works across machines and between people, and you can steer the agents from any device.&lt;/p&gt;

&lt;p&gt;Create a channel at &lt;a href="https://radio.plasma.ai/" rel="noopener noreferrer"&gt;radio.plasma.ai&lt;/a&gt;&lt;br&gt;
Copy the link&lt;br&gt;
Paste into one or more agents&lt;br&gt;
The agents join the channel. Send one message and they take it from there, replying to you and to each other&lt;br&gt;
The simplest way to think about a Radio channel is as a meeting. Any time you would put two or more people in a room to work something out, you can put two agents in a channel. There are three kinds, and each one builds on the last.&lt;/p&gt;

&lt;p&gt;Team meeting: All the agents on your computer in one channel. One agent writes the code while another tests it. Review a PR together, split up a task, debate a bug. Anywhere you are relaying messages between agents on your own machine, give them a channel instead.&lt;br&gt;
Cross-functional meeting: Agents in different places: your laptop, a cloud agent, a teammate's machine. Their Claude talks to your Claude, Codex, Grok, etc. The channel is the shared surface, so nobody has to be the go-between.&lt;br&gt;
External meeting: Everything above, now multiplayer. Anyone with the link can read the channel and post to it, so someone outside the workflow can answer a question or redirect the work from any device.&lt;br&gt;
In every case, the channel is also the record. Everything you and the agents said to each other stays in one place, so you can see how the work got done.&lt;/p&gt;

&lt;p&gt;Radio is a core primitive of our first product, Fractal.&lt;/p&gt;

&lt;p&gt;Fractal lets a team delegate real work to agents the way it would to people: pick the right model for each job, hand it a goal and a budget, and watch the work happen in real time.&lt;/p&gt;

&lt;p&gt;You can see other building blocks of Fractal on our &lt;a href="https://www.plasma.ai/research" rel="noopener noreferrer"&gt;Research page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Fractal is coming to the cloud. See the tree, steer its nodes, and track costs as the work runs.&lt;/p&gt;

&lt;p&gt;Early access opens soon.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>chatgpt</category>
    </item>
    <item>
      <title>Why Google’s Open Knowledge Format Matters for Developers and Content Creators?</title>
      <dc:creator>Vivek Maskara</dc:creator>
      <pubDate>Wed, 17 Jun 2026 02:24:11 +0000</pubDate>
      <link>https://dev.to/maskaravivek/why-googles-open-knowledge-format-matters-for-developers-and-content-creators-1mgk</link>
      <guid>https://dev.to/maskaravivek/why-googles-open-knowledge-format-matters-for-developers-and-content-creators-1mgk</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgdi0dijxp3hmztk58z7j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgdi0dijxp3hmztk58z7j.png" alt="Google’s Open Knowledge Format" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most teams don’t suffer from a lack of data. They suffer from a lack of shared context. Definitions, caveats, ownership, and “how to use this safely” guidance end up scattered across wikis, tickets, dashboards, and people’s heads. Google’s Open Knowledge Format (OKF) tackles that sprawl by packaging curated context as a portable, versionable bundle of “just files” that both humans and AI agents can consume reliably (&lt;a href="https://cloud.google.com/blog/products/data-analytics/how-the-open-knowledge-format-can-improve-data-sharing" rel="noopener noreferrer"&gt;Google Cloud Blog, Jun 12 2026&lt;/a&gt;, &lt;a href="https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md" rel="noopener noreferrer"&gt;OKF SPEC.md&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;This article covers the problem OKF targets, what the format is (and isn’t), and the building blocks that make it work. It then explains why OKF fits developer workflows and content operations, plus adoption patterns, tradeoffs, and practical next steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real problem OKF is trying to solve: “context sprawl”
&lt;/h2&gt;

&lt;p&gt;OKF exists because the most important knowledge in an organization often isn’t the data itself. It’s the context around the data, and that context is scattered. Google describes this as “metadata, context, and curated knowledge” spread across too many surfaces to share or reuse cleanly (&lt;a href="https://cloud.google.com/blog/products/data-analytics/how-the-open-knowledge-format-can-improve-data-sharing" rel="noopener noreferrer"&gt;Google Cloud Blog, Jun 12 2026&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;This fragmentation hurts humans. It hurts AI agents and cross-team work even more. People compensate with experience. They know which wiki is “more correct,” who to ask, and which dashboard is legacy. Agents don’t have that intuition. When context is missing or split across systems, an agent has to infer and guess. That leads to confident wrong answers and brittle automations.&lt;/p&gt;

&lt;p&gt;It also helps to separate &lt;strong&gt;data&lt;/strong&gt; from &lt;strong&gt;context&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data&lt;/strong&gt;: table schemas, API contracts, dbt models.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context&lt;/strong&gt;: metric definitions, caveats, ownership, join guidance, deprecations, and runbooks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without context, two teams can query the same table and still disagree on what “revenue” means.&lt;/p&gt;

&lt;p&gt;Before OKF, a developer debugging a revenue discrepancy might find SQL in a dashboard, a definition in a wiki page, and a deprecation note buried in a ticket. Then they repeat the same archaeology next week. OKF is positioned as a portable corpus that tools (and agents) can consume, not as “another portal.”&lt;/p&gt;

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

&lt;h2&gt;
  
  
  What OKF is (and what it is not)
&lt;/h2&gt;

&lt;p&gt;The simplest way to understand OKF is as a file-based packaging standard for curated knowledge. It’s small enough to live in git, structured enough to index, and neutral enough to move across tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  OKF in one sentence: a directory of Markdown + YAML frontmatter
&lt;/h3&gt;

&lt;p&gt;Per Google’s announcement and the specification, OKF is a vendor-neutral way to package curated organizational knowledge as a directory of Markdown files. Each file includes YAML frontmatter for structured metadata (&lt;a href="https://cloud.google.com/blog/products/data-analytics/how-the-open-knowledge-format-can-improve-data-sharing" rel="noopener noreferrer"&gt;Google Cloud Blog, Jun 12 2026&lt;/a&gt;, &lt;a href="https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md" rel="noopener noreferrer"&gt;OKF SPEC.md&lt;/a&gt;). The key idea is that it’s “just files.” There’s no required SDK, runtime, schema registry, or central service.&lt;/p&gt;

&lt;p&gt;Markdown plus YAML is a deliberate trade. Humans can review it in pull requests, and diffs stay readable. Tools can parse the frontmatter to index and retrieve concepts consistently.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;metric&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Revenue&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(Net)"&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Net&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;revenue&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;excluding&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;refunds&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;chargebacks."&lt;/span&gt;
&lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bigquery://analytics.metrics/revenue_net&lt;/span&gt;
&lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;finance&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;gaap&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;executive-dashboard&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-06-12T00:00:00Z&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="s"&gt;Metric notes, caveats, and links go here in Markdown.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What OKF is not: not a replacement for domain schemas or APIs
&lt;/h3&gt;

&lt;p&gt;The spec is explicit that OKF doesn’t replace domain-specific schemas or interface definitions such as Avro, Protobuf, OpenAPI, or dbt models (&lt;a href="https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md" rel="noopener noreferrer"&gt;OKF SPEC.md&lt;/a&gt;). Instead, it acts as a curation and metadata wrapper. It points at sources of truth and adds context they don’t capture well, like usage guidance and operational notes. This reduces schema duplication and helps avoid “two sources of truth.”&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api-endpoint&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST /v1/orders&lt;/span&gt;
&lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://api.example.com/openapi.yaml#/paths/~1v1~1orders/post&lt;/span&gt;
&lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;orders&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;public-api&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-06-12T00:00:00Z&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;Use `Idempotency-Key` for retries. Deprecated fields&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="s"&gt;promoCode` (use `discounts[]`).&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Common confusion: OKF vs Google Knowledge Graph / schema.org markup
&lt;/h3&gt;

&lt;p&gt;OKF is for internal, file-based knowledge bundles meant to be shared and used by tools and agents. The Google Knowledge Graph Search API is different. It’s a hosted service that returns entity results as JSON-LD using schema.org types (&lt;a href="https://developers.google.com/knowledge-graph" rel="noopener noreferrer"&gt;Knowledge Graph Search API docs&lt;/a&gt;). A quick check helps: OKF lives in repos and filesystems. Knowledge Graph is an online entity search API.&lt;/p&gt;

&lt;h2&gt;
  
  
  How OKF works: the building blocks developers can reason about
&lt;/h2&gt;

&lt;p&gt;Once you accept “it’s just files,” the next question is how those files become something tools can index, link, and retrieve. OKF’s answer is a small set of filesystem-friendly rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Knowledge Bundles, Concepts, and Concept IDs
&lt;/h3&gt;

&lt;p&gt;The spec defines a filesystem-native mental model (&lt;a href="https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md" rel="noopener noreferrer"&gt;OKF SPEC.md&lt;/a&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;Knowledge Bundle&lt;/strong&gt; is a distributable directory tree.&lt;/li&gt;
&lt;li&gt;Each &lt;strong&gt;Concept&lt;/strong&gt; is exactly one Markdown document.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Concept ID&lt;/strong&gt; is derived from the file path (minus &lt;code&gt;.md&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Path-based IDs stay stable under version control. They also make it easy for tools to reference concepts without inventing a separate identifier scheme.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;knowledge-bundle/
  datasets/
    ga4_ecommerce.md            # conceptId: datasets/ga4_ecommerce
  tables/
    ga4/events.md               # conceptId: tables/ga4/events
  metrics/
    revenue_net.md              # conceptId: metrics/revenue_net
  runbooks/
    revenue_discrepancy.md      # conceptId: runbooks/revenue_discrepancy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  YAML frontmatter as the “queryable surface area”
&lt;/h3&gt;

&lt;p&gt;OKF keeps structure intentionally small. Google highlights a compact set of frontmatter fields, &lt;code&gt;type&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;resource&lt;/code&gt;, &lt;code&gt;tags&lt;/code&gt;, &lt;code&gt;timestamp&lt;/code&gt;, that tools can index for filtering and routing (&lt;a href="https://cloud.google.com/blog/products/data-analytics/how-the-open-knowledge-format-can-improve-data-sharing" rel="noopener noreferrer"&gt;Google Cloud Blog, Jun 12 2026&lt;/a&gt;). The spec defines the normative rules (&lt;a href="https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md" rel="noopener noreferrer"&gt;OKF SPEC.md&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;This is what makes “retrieve all runbooks tagged incident-response” a metadata query instead of a full-text guessing game.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;metric&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Revenue (Net)&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Net revenue excluding refunds and chargebacks.&lt;/span&gt;
&lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bigquery://analytics.metrics/revenue_net&lt;/span&gt;
&lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;revenue&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;finance&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-06-12T00:00:00Z&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Markdown body as the “human-first, agent-usable” narrative layer
&lt;/h3&gt;

&lt;p&gt;OKF uses plain Markdown for the body. That keeps content readable in GitHub or internal doc sites. It also stays easy for agents to chunk, quote, and follow links. Links express relationships beyond folders. For example, a metric can point to its source table and the runbook for incidents.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;See source table: ../tables/ga4/events.md
Incident response: ../runbooks/revenue_discrepancy.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why OKF matters for developers: interoperability, workflows, and agent grounding
&lt;/h2&gt;

&lt;p&gt;With the mechanics in place, the developer value becomes clearer. OKF is a low-friction interchange format that fits engineering workflows while making agent grounding more dependable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interoperability without glue code: “format, not service”
&lt;/h3&gt;

&lt;p&gt;OKF matters to developers because it’s a format you can move around, not a platform you must integrate with. Bundles are “just files,” so producers and consumers only need filesystem access plus a Markdown/YAML parser (&lt;a href="https://cloud.google.com/blog/products/data-analytics/how-the-open-knowledge-format-can-improve-data-sharing" rel="noopener noreferrer"&gt;Google Cloud Blog, Jun 12 2026&lt;/a&gt;, &lt;a href="https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md" rel="noopener noreferrer"&gt;OKF SPEC.md&lt;/a&gt;). You can keep a bundle in git, publish a pinned tarball for reproducible runs, or mount it into an environment where tools and agents can read it.&lt;/p&gt;

&lt;p&gt;A concrete payoff is cross-stack reuse. A data platform team can publish a bundle once. Then a Python agent pipeline, a TypeScript IDE helper, and a catalog ingestion job can all consume the same directory tree.&lt;/p&gt;

&lt;h3&gt;
  
  
  “Knowledge as code” practices applied to context
&lt;/h3&gt;

&lt;p&gt;Because OKF is diffable text, it fits normal engineering discipline. Metric definitions and runbooks can be updated via PRs, reviewed by owners, and rolled back when needed. That turns “why did this number change?” into a git blame instead of a Slack archaeology expedition. The spec’s version-control friendliness is the enabling detail (&lt;a href="https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md" rel="noopener noreferrer"&gt;OKF SPEC.md&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;In practice, teams add CI gates that keep the bundle coherent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Example CI gate: fail if any concept link points to a missing file&lt;/span&gt;
python scripts/check_links.py knowledge-bundle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Better agent grounding: fewer hallucinations, more reliable automation
&lt;/h3&gt;

&lt;p&gt;Google frames OKF as a way to ground agents with the right context. That includes definitions, caveats, deprecations, and operational procedures (&lt;a href="https://cloud.google.com/blog/products/data-analytics/how-the-open-knowledge-format-can-improve-data-sharing" rel="noopener noreferrer"&gt;Google Cloud Blog, Jun 12 2026&lt;/a&gt;). Stable concept IDs plus queryable frontmatter let retrieval start narrow (by &lt;code&gt;type&lt;/code&gt; and &lt;code&gt;tags&lt;/code&gt;). Tools can then traverse links to assemble the “full story” for a task.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;runbook&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Revenue discrepancy triage&lt;/span&gt;
&lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;incident-response&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;revenue&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-06-12T00:00:00Z&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An incident bot can pull the runbook and route to the right owner. An analytics agent can retrieve the metric concept to avoid using the wrong definition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why OKF matters for content creators: an “LLM-wiki” you can ship
&lt;/h2&gt;

&lt;p&gt;Developers care about interoperability and workflow fit. Content creators care about portability, editorial control, and reuse across audiences and tools. OKF is designed to meet those needs without forcing a single publishing platform.&lt;/p&gt;

&lt;h3&gt;
  
  
  OKF as a standardized, portable “LLM-wiki” pattern
&lt;/h3&gt;

&lt;p&gt;OKF turns the “LLM-wiki” idea into something teams can adopt without committing to one wiki product. Google frames the format as a way to standardize that pattern: write human-readable Markdown, add a small amount of structured metadata, and ship the result as a bundle of files (&lt;a href="https://cloud.google.com/blog/products/data-analytics/how-the-open-knowledge-format-can-improve-data-sharing" rel="noopener noreferrer"&gt;Google Cloud Blog, Jun 12 2026&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That portability is the unlock. A docs team can curate a &lt;code&gt;product-concepts/&lt;/code&gt; bundle for feature behavior and deprecations. A data team can curate an &lt;code&gt;analytics-concepts/&lt;/code&gt; bundle for metrics, tables, and runbooks. You can publish both internally, open-source a subset, or share across org boundaries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Metadata that makes content discoverable and reusable
&lt;/h3&gt;

&lt;p&gt;YAML frontmatter is what makes OKF feel less like “a folder of docs” and more like a queryable knowledge base. Tags let you slice content by audience or lifecycle (&lt;code&gt;deprecated&lt;/code&gt;, &lt;code&gt;oncall&lt;/code&gt;, &lt;code&gt;exec&lt;/code&gt;). &lt;code&gt;resource&lt;/code&gt; anchors a concept to the system of record. &lt;code&gt;timestamp&lt;/code&gt; provides a freshness signal that search and agents can respect.&lt;/p&gt;

&lt;p&gt;Over time, editors can standardize templates per &lt;code&gt;type&lt;/code&gt;, so readers recognize the shape quickly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;runbook&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Payments latency spike&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Triage and mitigation steps for elevated p95 latency.&lt;/span&gt;
&lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://monitoring.example.com/dashboards/payments&lt;/span&gt;
&lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;incident-response&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;payments&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;oncall&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-06-12T00:00:00Z&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gu"&gt;## Symptoms&lt;/span&gt;
&lt;span class="gu"&gt;## Diagnosis&lt;/span&gt;
&lt;span class="gu"&gt;## Mitigation&lt;/span&gt;
&lt;span class="gu"&gt;## Escalation&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Publishing and maintenance workflows that don’t fight your tools
&lt;/h3&gt;

&lt;p&gt;Because OKF uses Markdown, it renders cleanly on GitHub/GitLab. It can also sync into an internal docs portal without rewriting content. Google highlights shipping bundles as artifacts, which maps well to editorial release trains (&lt;a href="https://cloud.google.com/blog/products/data-analytics/how-the-open-knowledge-format-can-improve-data-sharing" rel="noopener noreferrer"&gt;Google Cloud Blog, Jun 12 2026&lt;/a&gt;). For example, you can run a monthly “data platform bundle” release with changelogs, review rotations, and a style guide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical adoption patterns
&lt;/h2&gt;

&lt;p&gt;OKF is simple in theory. Adoption succeeds when you use that simplicity to enforce clarity, not to create near-duplicate docs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern: concept-per-file bundles organized by domain
&lt;/h3&gt;

&lt;p&gt;The spec’s “concept-per-file” rule is easiest to run when you organize by domain folders. Express relationships as links rather than mega-pages (&lt;a href="https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md" rel="noopener noreferrer"&gt;OKF SPEC.md&lt;/a&gt;). A simple starting tree keeps concepts small enough to reuse.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;knowledge-bundle/
  metrics/revenue_net.md
  tables/orders.md
  policies/refunds.md
  runbooks/revenue_discrepancy.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside &lt;code&gt;metrics/revenue_net.md&lt;/code&gt;, keep the YAML frontmatter queryable. Use Markdown links to connect the graph:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;metric&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Revenue (Net)&lt;/span&gt;
&lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bigquery://analytics.marts/revenue_net&lt;/span&gt;
&lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;revenue&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;finance&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-06-12T00:00:00Z&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

Derived from &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Orders table&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;../tables/orders.md&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;. Refund handling: &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Refund policy&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;../policies/refunds.md&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pattern: distribute OKF as an artifact (repo, release, filesystem)
&lt;/h3&gt;

&lt;p&gt;Because bundles are “just files,” distribution is a packaging decision. Use a git repo for collaboration and review. Publish tarball releases for pinned, reproducible consumption. Optionally sync to a shared mount for runtime access (&lt;a href="https://cloud.google.com/blog/products/data-analytics/how-the-open-knowledge-format-can-improve-data-sharing" rel="noopener noreferrer"&gt;Google Cloud Blog, Jun 12 2026&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;For agents, pin explicitly, for example: “use bundle v2026.06 for this workflow run.” That prevents retrieval from drifting mid-incident.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-czf&lt;/span&gt; knowledge-bundle.tgz knowledge-bundle/
&lt;span class="c"&gt;# attach knowledge-bundle.tgz to a release like v2026.06&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pattern: pair OKF with catalog ingestion (Google Cloud Knowledge Catalog example)
&lt;/h3&gt;

&lt;p&gt;Google says Google Cloud Knowledge Catalog can ingest OKF and serve it to agents (&lt;a href="https://cloud.google.com/blog/products/data-analytics/how-the-open-knowledge-format-can-improve-data-sharing" rel="noopener noreferrer"&gt;Google Cloud Blog, Jun 12 2026&lt;/a&gt;). This split is useful. OKF stays the interchange layer. Catalogs become optional UX and indexing layers, not the only home for context.&lt;/p&gt;

&lt;p&gt;For more background on the product direction, see Google’s Knowledge Catalog introduction (&lt;a href="https://cloud.google.com/blog/products/data-analytics/introducing-the-google-cloud-knowledge-catalog" rel="noopener noreferrer"&gt;Knowledge Catalog blog, Apr 22 2026&lt;/a&gt;).&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Tradeoffs and pitfalls: where teams stumble and how to avoid it
&lt;/h2&gt;

&lt;p&gt;OKF is intentionally minimal, which makes it portable. The work doesn’t disappear. It shifts into conventions and maintenance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Inconsistent conventions reduce interoperability
&lt;/h3&gt;

&lt;p&gt;Minimal structure is also a sharp edge. If every team invents its own &lt;code&gt;type&lt;/code&gt; values, tag styles, and linking habits, cross-bundle queryability drops. That undermines the standardization OKF is meant to enable (&lt;a href="https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md" rel="noopener noreferrer"&gt;OKF SPEC.md&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Treat conventions as part of the product:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Define a shared &lt;code&gt;type&lt;/code&gt; taxonomy.&lt;/li&gt;
&lt;li&gt;Use a controlled tag vocabulary (including casing).&lt;/li&gt;
&lt;li&gt;Require consistent Markdown sections per &lt;code&gt;type&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Standardize link patterns (relative links, concept-per-file).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A common failure mode is tag casing drift. One team writes &lt;code&gt;incident-response&lt;/code&gt;, another writes &lt;code&gt;IncidentResponse&lt;/code&gt;, and filters miss half the runbooks. Fix it with templates, review checklists, and CI that rejects nonconforming metadata.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# scripts/normalize_tags.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_frontmatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^---\n(.*?)\n---\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safe_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&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;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&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;span class="n"&gt;bad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;md&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rglob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*.md&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;fm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_frontmatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="n"&gt;tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fm&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tags&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;bad&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;bad&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Non-lowercase tags in:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bad&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Treating OKF as the system of record for schemas
&lt;/h3&gt;

&lt;p&gt;OKF does not replace domain schemas like Avro, Protobuf, or OpenAPI (&lt;a href="https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md" rel="noopener noreferrer"&gt;OKF SPEC.md&lt;/a&gt;). Use it to explain and operationalize them. Keep it focused on rationale, caveats, examples, deprecations, and safe usage guidance. A simple rule helps: &lt;code&gt;resource&lt;/code&gt; must point to the canonical source-of-truth location.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api-endpoint&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CreateOrder&lt;/span&gt;
&lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://repo.example.com/apis/orders/openapi.yaml#/paths/~1v1~1orders/post&lt;/span&gt;
&lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;orders&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;public-api&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-06-12T00:00:00Z&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="s"&gt;Retries require `Idempotency-Key`. `promoCode` is deprecated; use `discounts[]`.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Stale knowledge and missing freshness signals
&lt;/h3&gt;

&lt;p&gt;Bundles rot unless you operationalize freshness. Google calls out &lt;code&gt;timestamp&lt;/code&gt; as part of the structured fields (&lt;a href="https://cloud.google.com/blog/products/data-analytics/how-the-open-knowledge-format-can-improve-data-sharing" rel="noopener noreferrer"&gt;Google Cloud Blog, Jun 12 2026&lt;/a&gt;). Use it to power staleness dashboards and enforcement, especially for runbooks.&lt;/p&gt;

&lt;p&gt;Add ownership conventions, review SLAs, and automated reminders. For high-risk concepts, add a “Last verified” line in Markdown.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Fail CI if any incident runbook is older than 90 days&lt;/span&gt;
python scripts/check_staleness.py knowledge-bundle &lt;span class="nt"&gt;--type&lt;/span&gt; runbook &lt;span class="nt"&gt;--tag&lt;/span&gt; incident-response &lt;span class="nt"&gt;--max-age-days&lt;/span&gt; 90
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to get started: next steps for developers and content teams
&lt;/h2&gt;

&lt;p&gt;Treat OKF like any shared interface. Start with a small, high-value surface area. Lock down conventions early. Add enough automation to keep the bundle trustworthy.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Start small: pick one high-value domain and define conventions
&lt;/h3&gt;

&lt;p&gt;Start where missing context repeatedly burns time. Good candidates include your top executive metrics, the most-queried tables, or on-call runbooks. Keep the first bundle small. Prove the workflow (author → review → publish → consume) before scaling.&lt;/p&gt;

&lt;p&gt;Define conventions up front:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A minimal &lt;code&gt;type&lt;/code&gt; list you will actually use (for example &lt;code&gt;metric&lt;/code&gt;, &lt;code&gt;table&lt;/code&gt;, &lt;code&gt;runbook&lt;/code&gt;, &lt;code&gt;dataset&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;A tag vocabulary with consistent casing&lt;/li&gt;
&lt;li&gt;One Markdown template per &lt;code&gt;type&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Google points to sample bundles (GA4 e-commerce, Stack Overflow, Bitcoin) in its announcement. Keep the spec open while you implement so you follow the normative rules (&lt;a href="https://cloud.google.com/blog/products/data-analytics/how-the-open-knowledge-format-can-improve-data-sharing" rel="noopener noreferrer"&gt;Google Cloud Blog, Jun 12 2026&lt;/a&gt;, &lt;a href="https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md" rel="noopener noreferrer"&gt;OKF SPEC.md&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Add quality gates: validation, link checks, and publishing
&lt;/h3&gt;

&lt;p&gt;Because OKF requires no tooling, you can add lightweight CI checks that match your conventions. Common gates include required frontmatter fields, forbidden tags, and broken relative links. If you want stricter guarantees, add JSON Schema validation.&lt;/p&gt;

&lt;p&gt;A simple first gate is ensuring Concept IDs (path minus &lt;code&gt;.md&lt;/code&gt;) are unique and indexable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# scripts/index_concepts.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;

&lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&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;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dupes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;md&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rglob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*.md&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;cid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;relative_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;with_suffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;as_posix&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cid&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;dupes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;dupes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Duplicate concept IDs:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dupes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Indexed &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; concepts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Publish in two forms: rendered docs (GitHub Pages or an internal portal) and a pinned bundle artifact (release tarball) for agents.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Decide how agents will consume it
&lt;/h3&gt;

&lt;p&gt;Plan for multiple consumption modes without changing content. Options include direct filesystem reads, an indexing pipeline for RAG, or catalog ingestion. Google notes that Knowledge Catalog can ingest OKF and serve it to agents (&lt;a href="https://cloud.google.com/blog/products/data-analytics/how-the-open-knowledge-format-can-improve-data-sharing" rel="noopener noreferrer"&gt;Google Cloud Blog, Jun 12 2026&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;A practical retrieval strategy is “filter, then traverse”:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Filter by &lt;code&gt;type&lt;/code&gt; and &lt;code&gt;tags&lt;/code&gt; (for example, &lt;code&gt;type: dataset&lt;/code&gt; plus a domain tag).&lt;/li&gt;
&lt;li&gt;Follow links dataset → tables → metrics → runbooks.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;OKF’s core promise is durability. Once your context lives in a knowledge bundle with consistent metadata and links, you can switch catalogs, search layers, and agent frameworks without rewriting the knowledge itself.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>googlecloud</category>
      <category>openknowledgeformat</category>
      <category>contentwriting</category>
    </item>
    <item>
      <title>You’re Already Using Git Worktrees. You Should Understand Them.</title>
      <dc:creator>Vivek Maskara</dc:creator>
      <pubDate>Mon, 18 May 2026 12:18:41 +0000</pubDate>
      <link>https://dev.to/maskaravivek/youre-already-using-git-worktrees-you-should-understand-them-4nh7</link>
      <guid>https://dev.to/maskaravivek/youre-already-using-git-worktrees-you-should-understand-them-4nh7</guid>
      <description>&lt;p&gt;If you’ve ever run &lt;a href="https://git-scm.com/docs/git-clone" rel="noopener noreferrer"&gt;&lt;code&gt;git clone&lt;/code&gt;&lt;/a&gt; and then started editing files in the resulting folder, you’ve been using a Git worktree all along. Git just doesn’t call it that in day-to-day conversation.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git worktree&lt;/code&gt; is the feature that lets you add &lt;em&gt;more&lt;/em&gt; checkouts (more folders with files on disk) that all share the same underlying repository history. Done well, it replaces the “stash/switch/stash-back” dance with something simpler: keep your in-progress work open in one directory, and do the urgent/clean/experimental thing in another.&lt;/p&gt;

&lt;p&gt;This is a mental-model article: what a worktree &lt;em&gt;is&lt;/em&gt;, why Git enforces “one branch per worktree,” and the few lifecycle commands that keep worktrees from turning into confusing “already checked out” / stale-metadata problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Mental Model: Shared History, Separate Working State
&lt;/h2&gt;

&lt;p&gt;Git’s own &lt;a href="https://git-scm.com/docs/git-worktree" rel="noopener noreferrer"&gt;&lt;code&gt;git worktree&lt;/code&gt; docs&lt;/a&gt; describe the feature as managing “multiple working trees attached to the same repository.” The twist is that you always start with one: the directory you land in after a normal (non-bare) clone is the &lt;strong&gt;main worktree&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Linked worktrees are additional working directories attached to the same repo. They &lt;strong&gt;share&lt;/strong&gt; the object database (commits/trees/blobs), but each worktree has its own working state: checked-out files, its own &lt;a href="https://git-scm.com/docs/gitglossary#def_HEAD" rel="noopener noreferrer"&gt;&lt;code&gt;HEAD&lt;/code&gt;&lt;/a&gt;, and its own index (staging area).&lt;/p&gt;

&lt;p&gt;That’s the whole trick:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shared:&lt;/strong&gt; history and objects (you don’t redownload Git data like a second clone).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per worktree:&lt;/strong&gt; files-on-disk + &lt;code&gt;HEAD&lt;/code&gt; + index (you don’t stomp on your other checkout).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One nuance that matters: a worktree isn’t its own independent “repo.” Most refs and remote configuration are shared. When you &lt;code&gt;git fetch&lt;/code&gt;, you’re updating the shared repository data that all worktrees see. That’s what makes worktrees lighter than multiple clones—but it’s also why Git has to be careful about how a branch name maps to a checkout.&lt;/p&gt;

&lt;p&gt;If you want a deeper explanation of why “objects live once” makes this efficient, the Git book’s “Git Objects” chapter is a good reference: &lt;a href="https://git-scm.com/book/en/v2/Git-Internals-Git-Objects" rel="noopener noreferrer"&gt;Git Internals: Git Objects&lt;/a&gt;.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  What Actually Changes on Disk when You Add a Worktree
&lt;/h3&gt;

&lt;p&gt;Git keeps the shared repository database in one place, then tracks per-worktree state separately. That’s why it can add a second checkout without cloning.&lt;/p&gt;

&lt;p&gt;If you’ve ever noticed that &lt;code&gt;.git&lt;/code&gt; sometimes looks like a &lt;em&gt;file&lt;/em&gt; (not a folder), that’s often Git pointing your working tree at the real “gitdir.” Worktrees lean on this idea heavily: each worktree has its own admin area, but the objects are shared.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Rule that Surprises People: One Branch per Worktree
&lt;/h2&gt;

&lt;p&gt;If you try to attach the &lt;em&gt;same&lt;/em&gt; branch to two worktrees, Git will usually refuse with “branch is already checked out.” That refusal is protection.&lt;/p&gt;

&lt;p&gt;The core reason is that a branch name is just a movable pointer. If two working directories could both “be” &lt;code&gt;feature&lt;/code&gt;, the branch pointer could move in one directory while the other directory still has older files on disk. Now you have a branch name that no longer uniquely identifies a single working state.&lt;/p&gt;

&lt;p&gt;Said differently: Git is fine with multiple &lt;em&gt;worktrees&lt;/em&gt; having different &lt;code&gt;HEAD&lt;/code&gt;s (each checkout is independent), but a branch ref like &lt;code&gt;refs/heads/feature&lt;/code&gt; is shared at the repository level. Letting two worktrees both “own” the same branch would create a footgun where “what commit is &lt;code&gt;feature&lt;/code&gt;?” depends on which folder last advanced it.&lt;/p&gt;

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

&lt;p&gt;When you want “two directories, same starting point,” Git’s safer options are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Create a new branch name&lt;/strong&gt; for the second worktree:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git worktree add ../wt-fix &lt;span class="nt"&gt;-b&lt;/span&gt; fix-branch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use a detached &lt;code&gt;HEAD&lt;/code&gt;&lt;/strong&gt; when you don’t intend to keep the work long-term:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git worktree add &lt;span class="nt"&gt;-d&lt;/span&gt; ../wt-scratch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Detached &lt;code&gt;HEAD&lt;/code&gt; is the “I want another directory at this commit, but I’m not claiming a branch name” mode. That’s why it’s safe: you can still make commits, but you’re not advancing a shared branch ref unless you later create one.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--force&lt;/code&gt; exists for edge cases, but it’s opting out of a guardrail. If you don’t already know &lt;em&gt;why&lt;/em&gt; you need it, you probably don’t.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Worktrees Shine (in Practice)
&lt;/h2&gt;

&lt;p&gt;Worktrees are a productivity primitive whenever parallelism beats context switching. Three common patterns:&lt;/p&gt;

&lt;h3&gt;
  
  
  1) Hotfix without Touching Your In-Progress Directory
&lt;/h3&gt;

&lt;p&gt;When a production issue lands mid-feature, create a new worktree on a hotfix branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git worktree add ../wt-hotfix &lt;span class="nt"&gt;-b&lt;/span&gt; hotfix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get a clean checkout in a new folder, and your original directory stays exactly as it was. When you’re done, remove it cleanly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git worktree remove ../wt-hotfix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.git-tower.com/learn/git/faq/git-worktree" rel="noopener noreferrer"&gt;Tower’s Git worktree FAQ&lt;/a&gt; describes this as one of the “default” worktree use cases, and it’s the one most people feel immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  2) PR Reviews and Repros in a Clean Sandbox
&lt;/h3&gt;

&lt;p&gt;You can attach a worktree directly to a remote-tracking branch and keep it around for as long as the review takes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git worktree add ../wt-review origin/some-pr-branch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3) Scratch Experiments You Don’t Want to Keep
&lt;/h3&gt;

&lt;p&gt;Detached worktrees are great for “let me try something” work without committing to a branch name yet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git worktree add &lt;span class="nt"&gt;-d&lt;/span&gt; ../wt-scratch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you end up making commits you want to keep, create a branch before you delete the folder so you don’t lose track of them.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Quick Note on “Why Not Just Clone Twice?”
&lt;/h3&gt;

&lt;p&gt;Separate clones can absolutely solve the same “two directories” problem. The difference is mainly cost and coupling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clones duplicate setup (another fetch/clone, another &lt;code&gt;.git&lt;/code&gt; object store).&lt;/li&gt;
&lt;li&gt;Worktrees share Git objects, but still behave like separate checkouts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you need complete isolation (different remotes, radically different Git config, or you want to move one copy around freely), a second clone can still be the simplest answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping Your Directories Sane
&lt;/h2&gt;

&lt;p&gt;The simplest convention (and the one that avoids the “oops, I edited the wrong checkout” mistake) is: keep linked worktrees &lt;em&gt;outside&lt;/em&gt; the main project folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;repo/            # main worktree
repo-hotfix/     # linked worktree
repo-review/     # linked worktree
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you do this often, encode intent in the directory name so it’s searchable later (&lt;code&gt;wt-hotfix-1234&lt;/code&gt;, &lt;code&gt;wt-pr-998&lt;/code&gt;, &lt;code&gt;wt-bisect-crash&lt;/code&gt;) instead of generic &lt;code&gt;tmp/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you want all worktrees contained under one parent folder, a popular layout is “bare repo + many worktrees” (see &lt;a href="https://nicknisi.com/posts/git-worktrees/" rel="noopener noreferrer"&gt;Nick Nisi&lt;/a&gt; and &lt;a href="https://gabri.me/blog/git-worktrees-done-right" rel="noopener noreferrer"&gt;Ahmed El Gabri&lt;/a&gt;). For background on bare repositories themselves, the Git book chapter is a good reference: &lt;a href="https://git-scm.com/book/en/v2/Git-Basics-Getting-a-Git-Repository" rel="noopener noreferrer"&gt;Git Basics: Getting a Git Repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fajmkw6cycj8yplij2ayq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fajmkw6cycj8yplij2ayq.png" alt="a conceptual folder layout illustration comparing “sibling worktrees” vs “bare repo + worktrees inside one folder.”" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you try the “bare repo + worktrees” approach, keep the tradeoff in mind: you’re saving Git object duplication, not environment duplication. Two worktrees can still mean two &lt;code&gt;node_modules/&lt;/code&gt; folders, two build outputs, and two IDE indexes. If setup dominates your workflow, keep only the worktrees you’re actively using.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Lifecycle Gotchas (and the 5 Commands that Prevent Them)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Removing vs Deleting (Stale Metadata)
&lt;/h3&gt;

&lt;p&gt;Deleting a worktree folder in Finder isn’t the same as telling Git it’s gone. Prefer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git worktree remove ../wt-review
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you already deleted the folder, clean up the leftover admin state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git worktree prune
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git can also garbage-collect stale worktree metadata automatically over time (controlled by &lt;code&gt;gc.worktreePruneExpire&lt;/code&gt;). That’s helpful, but it’s not a substitute for removing worktrees intentionally when you’re done.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  “This Branch Is Already Checked Out”
&lt;/h3&gt;

&lt;p&gt;This is Git enforcing “one branch per worktree.” Your usual fixes are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;make a new branch name (&lt;code&gt;-b&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;use detached &lt;code&gt;HEAD&lt;/code&gt; (&lt;code&gt;-d&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re debugging “why won’t Git let me…”, list the current attachments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git worktree list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Moving Worktrees (and Repairing when Paths Change)
&lt;/h3&gt;

&lt;p&gt;If you need to reorganize, prefer Git’s commands over dragging folders around in your file manager.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;git worktree move &amp;lt;worktree&amp;gt; &amp;lt;new-path&amp;gt;&lt;/code&gt; relocates a linked worktree (you can’t move the main worktree).&lt;/li&gt;
&lt;li&gt;If you moved things manually and links broke, &lt;code&gt;git worktree repair&lt;/code&gt; can reestablish the connection in supported cases.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Intermittent Drives: Lock Before Git Prunes
&lt;/h3&gt;

&lt;p&gt;If a worktree lives on an external drive or network mount, Git may treat it as “missing” when the volume isn’t mounted. Use &lt;a href="https://git-scm.com/docs/git-worktree" rel="noopener noreferrer"&gt;&lt;code&gt;git worktree lock&lt;/code&gt;&lt;/a&gt; to prevent surprises:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git worktree lock &lt;span class="nt"&gt;--reason&lt;/span&gt; &lt;span class="s2"&gt;"On external drive"&lt;/span&gt; /Volumes/SSD/wt-release
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cheat Sheet
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git worktree add ../wt-name &lt;span class="nt"&gt;-b&lt;/span&gt; my-branch   &lt;span class="c"&gt;# new worktree + new branch&lt;/span&gt;
git worktree add ../wt-name origin/branch  &lt;span class="c"&gt;# worktree from remote-tracking branch&lt;/span&gt;
git worktree add &lt;span class="nt"&gt;-d&lt;/span&gt; ../wt-scratch          &lt;span class="c"&gt;# detached-HEAD scratch worktree&lt;/span&gt;
git worktree list                          &lt;span class="c"&gt;# see what's attached&lt;/span&gt;
git worktree remove ../wt-name             &lt;span class="c"&gt;# remove cleanly&lt;/span&gt;
git worktree prune                         &lt;span class="c"&gt;# cleanup after manual deletion&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;Worktrees aren’t a niche add-on. They’re the name for the checkout you already use, plus a way to add more checkouts without cloning again. Keep the mental model in your head—shared history, separate working state—and Git’s guardrails (like one branch per worktree) stop feeling arbitrary and start feeling helpful.&lt;/p&gt;

&lt;p&gt;If you learned something from this post, give me a follow and checkout the AI powered &lt;a href="https://mdedit.ai/" rel="noopener noreferrer"&gt;markdown editor&lt;/a&gt; that i am building on the side.&lt;/p&gt;

</description>
      <category>git</category>
      <category>gitworktree</category>
    </item>
  </channel>
</rss>
