<?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: Alisa Hester</title>
    <description>The latest articles on DEV Community by Alisa Hester (@alisa_hester_1).</description>
    <link>https://dev.to/alisa_hester_1</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%2F3770936%2Fb5e6a116-20ae-4101-8980-76892ae31c65.png</url>
      <title>DEV Community: Alisa Hester</title>
      <link>https://dev.to/alisa_hester_1</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alisa_hester_1"/>
    <language>en</language>
    <item>
      <title>OpenAI Prompt Caching: When Is the New 12-Hour Cache Cheaper?</title>
      <dc:creator>Alisa Hester</dc:creator>
      <pubDate>Tue, 27 Oct 2026 21:55:27 +0000</pubDate>
      <link>https://dev.to/alisa_hester_1/openai-prompt-caching-when-is-the-new-12-hour-cache-cheaper-4kka</link>
      <guid>https://dev.to/alisa_hester_1/openai-prompt-caching-when-is-the-new-12-hour-cache-cheaper-4kka</guid>
      <description>&lt;p&gt;Your prompt caching dashboard shows a healthy read hit rate. The inference bill stays high anyway. Then OpenAI ships &lt;a href="https://developers.openai.com/api/docs/changelog" rel="noopener noreferrer"&gt;12-hour prompt caching&lt;/a&gt;, and someone on the team asks whether you should switch.&lt;/p&gt;

&lt;p&gt;On GPT-5.6 and later, a 12-hour (&lt;code&gt;12h&lt;/code&gt;) cache write costs 2.5x the input price, while a write at the default 30-minute (&lt;code&gt;30m&lt;/code&gt;) lifetime costs 1.25x. So 12h only pays when the same unchanged prefix comes back after two or more gaps longer than 30 minutes inside a 12-hour window. Otherwise 30m is cheaper, even when 12h shows a higher hit rate.&lt;/p&gt;

&lt;p&gt;Hit rate counts reads. It says nothing about what you paid to write the prefix, and that write price now depends on the retention you pick. You can price each prefix from your own request logs with one rule and about 30 lines of Python.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;On GPT-5.6 and later in the Responses API, you choose a 30-minute or 12-hour minimum lifetime, and the 12-hour write costs twice as much.&lt;/li&gt;
&lt;li&gt;Count the gaps longer than 30 minutes after which the same prefix is reused inside one 12-hour window. With zero or one, 30m is cheaper. From two, 12h is cheaper.&lt;/li&gt;
&lt;li&gt;A higher hit rate can come with a higher bill. In the modeled scenarios, 12h lifts the hit rate by five points and still costs more in one case, and ties on hit rate while costing 36% more in another.&lt;/li&gt;
&lt;li&gt;Decide from cost per prefix per window, calibrated with your own usage fields.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What changed on October 20, 2026
&lt;/h2&gt;

&lt;p&gt;OpenAI's changelog entry reads: &lt;a href="https://developers.openai.com/api/docs/changelog" rel="noopener noreferrer"&gt;"You can now choose 12-hour prompt caching in the API. 12-hour prompt caching keeps shared instructions, tools, and context cached longer to benefit from lower cached-input pricing, even when requests are hours apart. 12-hour cache writes cost 2.5x the input token price."&lt;/a&gt; The entry is tagged for the Responses API, and the two model generations now follow different rules.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Models&lt;/th&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Values&lt;/th&gt;
&lt;th&gt;Write charge&lt;/th&gt;
&lt;th&gt;What the lifetime means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5.6 and later&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;prompt_cache_options.ttl&lt;/code&gt; (Responses API)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;30m&lt;/code&gt; (default) or &lt;code&gt;12h&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;1.25x input for 30m, 2.5x for 12h&lt;/td&gt;
&lt;td&gt;A minimum lifetime for read-price eligibility&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Earlier models&lt;/td&gt;
&lt;td&gt;&lt;code&gt;prompt_cache_retention&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;in_memory&lt;/code&gt; or &lt;code&gt;24h&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;A best-effort maximum&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;a href="https://developers.openai.com/api/docs/guides/prompt-caching" rel="noopener noreferrer"&gt;prompt caching guide&lt;/a&gt; says the older &lt;code&gt;24h&lt;/code&gt; setting "typically keeps entries available for around 30 minutes and can retain them for up to 24 hours." Twenty-four hours is the ceiling. The new 12 hours is a floor: for that long, a matching prefix is eligible for the cached-input price. Since May 29, 2026, &lt;code&gt;24h&lt;/code&gt; has been the default for organizations without zero data retention (ZDR), with no write charge.&lt;/p&gt;

&lt;p&gt;The guide adds: &lt;a href="https://developers.openai.com/api/docs/guides/prompt-caching" rel="noopener noreferrer"&gt;"The minimum lifetime governs eligibility for cache-read pricing. It does not guarantee that every request reuses stored KV tensors or has lower latency."&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How OpenAI prompt caching bills a prefix
&lt;/h2&gt;

&lt;p&gt;The cache stores &lt;a href="https://developers.openai.com/api/docs/guides/prompt-caching" rel="noopener noreferrer"&gt;key-value (KV) tensors for a reusable prefix&lt;/a&gt;, the unchanged tokens at the start of the prompt. It does not store the tokens themselves, and caching "does not change how the model generates output tokens." On GPT-5.6 and later, the minimum cacheable prompt is 1,024 tokens. Caching is on by default for supported models, and the guide documents no way to turn it off.&lt;/p&gt;

&lt;p&gt;You control where the prefix ends. With &lt;code&gt;mode&lt;/code&gt; set to &lt;code&gt;implicit&lt;/code&gt;, OpenAI places a breakpoint at the end of the latest eligible message. With &lt;code&gt;explicit&lt;/code&gt;, you place your own breakpoints, and each request can create up to four cache writes. Tokens after the last breakpoint are billed at the uncached input rate with no write charge.&lt;/p&gt;

&lt;p&gt;Each prefix then meets three prices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The write: 1.25x the input rate for 30m, 2.5x for 12h.&lt;/li&gt;
&lt;li&gt;The read: 0.1x the input rate on most of these models, and 0.05x on &lt;code&gt;gpt-6.1-sol&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The rewrite: a full new write when a request arrives after coverage has ended.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The guide works one example: &lt;a href="https://developers.openai.com/api/docs/guides/prompt-caching" rel="noopener noreferrer"&gt;"At the 0.1× read rate with 30-minute writes, writing a prefix once and fully reusing it once costs 1.35× its ordinary input cost, compared with 2× for processing it twice without caching."&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Reads also extend coverage. In the guide's words, "A 30-minute read refreshes coverage to at least 30 minutes after that read. If it uses an entry with valid 12-hour coverage, it preserves the later of the existing expiry and 30 minutes after the read. It does not restart a 12-hour clock or incur another write charge." Upgrading valid 30-minute coverage to 12 hours charges a new 12-hour write, with no credit for the earlier write.&lt;/p&gt;

&lt;p&gt;The prefix must also stay identical. Changes to &lt;code&gt;tools&lt;/code&gt;, &lt;code&gt;text.format&lt;/code&gt;, &lt;code&gt;reasoning.effort&lt;/code&gt;, and &lt;code&gt;text.verbosity&lt;/code&gt; affect the cached prefix, so keep stable instructions and shared reference material first and variable content at the end.&lt;/p&gt;

&lt;h2&gt;
  
  
  The break-even rule
&lt;/h2&gt;

&lt;p&gt;The guide frames the choice in one line: &lt;a href="https://developers.openai.com/api/docs/guides/prompt-caching" rel="noopener noreferrer"&gt;"Compare the higher 12-hour write rate with the cost of rewriting the prefix after 30-minute coverage expires."&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With 30m, every long gap costs a fresh write at 1.25x. With 12h, you pay 2.5x once and every long gap costs only a read, so from the second long gap 12h costs less.&lt;/p&gt;

&lt;p&gt;Write the cost of one prefix over one 12-hour window as a multiple of its ordinary input cost:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;30m: 1.25 × (1 + G)&lt;/li&gt;
&lt;li&gt;12h: 2.5 + (r × G)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The inputs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;G (measured, from your trace): the number of gaps longer than 30 minutes after which the same unchanged prefix is reused inside one 12-hour window.&lt;/li&gt;
&lt;li&gt;r (from the guide and the price list): the read rate, 0.1 on most GPT-5.6 and later models or 0.05 on &lt;code&gt;gpt-6.1-sol&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;1.25 and 2.5 (from the guide): the write multipliers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reads inside a burst cost the same under both options, so they drop out. At G = 0, it is 1.25 against 2.5. At G = 1, it is 2.5 against 2.55 (r = 0.05) or 2.6 (r = 0.1), so 30m still wins. At G = 2, it is 3.75 against 2.6 or 2.7, so 12h wins.&lt;/p&gt;

&lt;p&gt;A service that runs longer than 12 hours needs a new 12h write in each window, because reads do not restart the 12-hour clock. The openclaw maintainers merged 12h support the day after launch as an opt-in, with write-price accounting, in &lt;a href="https://github.com/openclaw/openclaw/pull/178695" rel="noopener noreferrer"&gt;their pull request to opt into 12-hour prompt caching&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five modeled scenarios: an illustration with list prices, not a benchmark
&lt;/h2&gt;

&lt;p&gt;The numbers in this section come from a replay model run on OpenAI list prices. They are not results from production traffic, and the replay model makes no API calls. Every scenario uses these assumptions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prices for &lt;code&gt;gpt-6.1-sol&lt;/code&gt;, Standard tier, short context, per 1M tokens, from &lt;a href="https://developers.openai.com/api/docs/pricing" rel="noopener noreferrer"&gt;OpenAI's pricing page&lt;/a&gt;: input $2.00, cached input $0.10, cache write 30m $2.50, cache write 12h $5.00, output $10.00.&lt;/li&gt;
&lt;li&gt;One stable 20,000-token prefix (system prompt, tool definitions, reference material) with an explicit breakpoint at its end.&lt;/li&gt;
&lt;li&gt;1,000 variable input tokens after the breakpoint per request, at the uncached input rate with no write charge.&lt;/li&gt;
&lt;li&gt;500 output tokens per request.&lt;/li&gt;
&lt;li&gt;Every request inside coverage is a read.&lt;/li&gt;
&lt;li&gt;A read extends coverage to at least 30 minutes after it and never restarts the 12-hour clock.&lt;/li&gt;
&lt;li&gt;A request after coverage ends is a fresh write.&lt;/li&gt;
&lt;li&gt;"No cache" is a reference line only, with every prefix token at the input rate. It is not a setting OpenAI documents.&lt;/li&gt;
&lt;li&gt;Hit rate is the share of requests that read the prefix.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Modeled illustration with OpenAI list prices (&lt;code&gt;gpt-6.1-sol&lt;/code&gt;, Standard tier). Not a benchmark.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Requests&lt;/th&gt;
&lt;th&gt;No-cache reference&lt;/th&gt;
&lt;th&gt;30m cost and hit rate&lt;/th&gt;
&lt;th&gt;12h cost and hit rate&lt;/th&gt;
&lt;th&gt;Cheaper option&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A. Steady: one request every 2 minutes for 8 hours (G = 0)&lt;/td&gt;
&lt;td&gt;240&lt;/td&gt;
&lt;td&gt;$11.2800&lt;/td&gt;
&lt;td&gt;$2.2080, 239/240 (99.6%)&lt;/td&gt;
&lt;td&gt;$2.2580, 239/240 (99.6%)&lt;/td&gt;
&lt;td&gt;30m, by $0.05&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B. Bursty: six bursts of 10 requests 30 seconds apart, bursts start 90 minutes apart (G = 5)&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;$2.8200&lt;/td&gt;
&lt;td&gt;$0.8280, 54/60 (90.0%)&lt;/td&gt;
&lt;td&gt;$0.6380, 59/60 (98.3%)&lt;/td&gt;
&lt;td&gt;12h, 23% cheaper&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C. Sparse: four requests three hours apart (G = 3)&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;$0.1880&lt;/td&gt;
&lt;td&gt;$0.2280, 0/4 (0%)&lt;/td&gt;
&lt;td&gt;$0.1340, 3/4 (75.0%)&lt;/td&gt;
&lt;td&gt;12h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D. Same bursts as B, but the prefix changes before each burst (G = 0 for every prefix)&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;$2.8200&lt;/td&gt;
&lt;td&gt;$0.8280, 54/60 (90.0%)&lt;/td&gt;
&lt;td&gt;$1.1280, 54/60 (90.0%)&lt;/td&gt;
&lt;td&gt;30m (12h costs 36% more)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E. Two bursts of 10 requests, two hours apart (G = 1)&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;$0.9400&lt;/td&gt;
&lt;td&gt;$0.2760, 18/20 (90.0%)&lt;/td&gt;
&lt;td&gt;$0.2780, 19/20 (95.0%)&lt;/td&gt;
&lt;td&gt;30m, by $0.002&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In every row, the cheaper option is the one the break-even rule predicts from G. In A and D the hit rates tie while the costs differ, and in E the higher hit rate belongs to the more expensive option.&lt;/p&gt;

&lt;p&gt;In C, every 30m request is a write at 1.25x, so 30m costs more than the no-cache line. D is the deploy case: a release edits tool definitions, or a timestamp sits in the system prompt, so each burst starts with a new prefix and the 12-hour coverage on the old one goes unused.&lt;/p&gt;

&lt;h2&gt;
  
  
  The replay model behind the table
&lt;/h2&gt;

&lt;p&gt;The script replays request times and prefix versions against the billing rules and prices in the assumptions. The trace at the end is Scenario E.&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;# Replay a request trace against OpenAI's documented cache billing for GPT-5.6+.
# Prices: USD per 1M tokens, gpt-6.1-sol, Standard tier, checked 2026-10-22.
&lt;/span&gt;&lt;span class="n"&gt;PRICES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cached_input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;write_30m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;write_12h&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;5.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;10.00&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;replay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prefix_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tail_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;PRICES&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;trace: list of (minute, prefix_version). ttl: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;30m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;12h&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; or None.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;life&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;30m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;12h&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;720&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="n"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;expiry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;  &lt;span class="c1"&gt;# prefix_version -&amp;gt; minute the coverage ends
&lt;/span&gt;    &lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;trace&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;ttl&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# reference only: every prefix token at the input rate
&lt;/span&gt;            &lt;span class="n"&gt;cost&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;prefix_tokens&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;expiry&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="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# read
&lt;/span&gt;            &lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="n"&gt;cost&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;prefix_tokens&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cached_input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;expiry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expiry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# write
&lt;/span&gt;            &lt;span class="n"&gt;cost&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;prefix_tokens&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;write_&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;expiry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;life&lt;/span&gt;
        &lt;span class="n"&gt;cost&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;tail_tokens&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;output_tokens&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;

&lt;span class="c1"&gt;# Two bursts of 10 requests, two hours apart, same 20,000-token prefix.
&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v1&lt;/span&gt;&lt;span class="sh"&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;start&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&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;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&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;ttl&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;30m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;12h&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;replay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running it prints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;None (0.94, 0)
30m (0.276, 18)
12h (0.278, 19)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To reproduce the other rows, change the trace; for D, give each burst its own prefix version. For your own workload, use timestamps and a prefix hash from your logs, and the current prices for your model.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the replay model does not show
&lt;/h2&gt;

&lt;p&gt;The replay model is optimistic. It assumes every request inside coverage is a read, and real traffic can miss. In a small test of &lt;code&gt;gpt-5.5&lt;/code&gt; (a model from before GPT-5.6) in the Responses API, Jangwook Kim found that on a prompt of about 3,700 tokens &lt;a href="https://dev.to/jangwook_kim_e31e7291ad98/openais-24h-prompt-cache-we-measured-the-real-discount-2c0n"&gt;"roughly 1 in 4 calls still paid full price"&lt;/a&gt; (3 of 12 warm calls). The sample is small and predates GPT-5.6, so calibrate the replay model with the &lt;code&gt;cached_tokens&lt;/code&gt; and &lt;code&gt;cache_write_tokens&lt;/code&gt; your own responses report.&lt;/p&gt;

&lt;p&gt;The replay model has other limits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No latency numbers. OpenAI's prompt caching guide does not quantify latency gains, and a minimum lifetime does not guarantee lower latency.&lt;/li&gt;
&lt;li&gt;Standard tier only. This replay uses the Standard-tier prices listed above. Recalculate with the current price table before using another tier.&lt;/li&gt;
&lt;li&gt;Responses API only. OpenAI documents the 12-hour option there, so do not plan on it for Chat Completions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Testing the counterargument: more hits can only help
&lt;/h2&gt;

&lt;p&gt;The case for switching: longer retention means more requests find the prefix warm, every extra hit is a read at a tenth of the input price or less, so turning 12h on can only help. On models before GPT-5.6, that holds. The longer &lt;code&gt;24h&lt;/code&gt; setting has no write charge, so extra retention costs nothing.&lt;/p&gt;

&lt;p&gt;On GPT-5.6 and later it holds only when a prefix returns after two or more long gaps, because the longer option doubles the write price. Scenario E shows the small case: 12h raises the hit rate from 90.0% to 95.0%, and the bill rises by $0.002, under 1% of that trace. Scenario D shows the large case: the hit rates tie at 90.0%, and 12h costs 36% more.&lt;/p&gt;

&lt;p&gt;Tian Pan described this pattern before the 12-hour option existed, writing about Anthropic prices: teams who &lt;a href="https://tianpan.co/blog/2026-04-17-prompt-cache-break-even-math" rel="noopener noreferrer"&gt;"monitor the cache hit rate counter going up, and assume they're saving money. Some of them are paying more than if they hadn't cached anything."&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What to log per request
&lt;/h2&gt;

&lt;p&gt;Your dashboard gives you hit rate. The decision needs cost per prefix per window. To get it, log these fields for every request:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Timestamp.&lt;/li&gt;
&lt;li&gt;Model and region.&lt;/li&gt;
&lt;li&gt;A hash or version label of the prefix.&lt;/li&gt;
&lt;li&gt;Billed input: total input tokens, from &lt;code&gt;usage.input_tokens&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cached_tokens&lt;/code&gt;, from &lt;code&gt;usage.input_tokens_details&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cache_write_tokens&lt;/code&gt;, with its &lt;code&gt;cache_write_tokens_details&lt;/code&gt; split into &lt;code&gt;ttl_30m_tokens&lt;/code&gt; and &lt;code&gt;ttl_12h_tokens&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Output tokens.&lt;/li&gt;
&lt;li&gt;Latency to first token, measured on your side.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compute cost per request by pricing each bucket separately: uncached input, cached reads, 30-minute writes, 12-hour writes, and output. If you already track &lt;a href="https://ekline.io/blog/ai-agent-pricing-billable-outcome" rel="noopener noreferrer"&gt;what each agent attempt costs&lt;/a&gt;, this is the caching line inside that number. Then group requests by prefix hash and 12-hour window, count G for each group, and compare the two formulas.&lt;/p&gt;

&lt;p&gt;When a prefix misses unexpectedly, &lt;a href="https://developers.openai.com/api/docs/guides/prompt-caching/diagnostics" rel="noopener noreferrer"&gt;Prompt Cache Diagnostics&lt;/a&gt; compares a response with an earlier one through &lt;code&gt;prompt_cache_options.comparison_response_id&lt;/code&gt; and returns a miss &lt;code&gt;reason&lt;/code&gt; such as &lt;code&gt;tools_changed&lt;/code&gt; or &lt;code&gt;model_changed&lt;/code&gt;. It works on GPT-5.6 and later in the Responses API.&lt;/p&gt;

&lt;p&gt;Know the limits of what you collect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sample size: a few hours of traffic will not show a full week's gap pattern.&lt;/li&gt;
&lt;li&gt;Diagnostics records expire after a short period, so investigate misses soon.&lt;/li&gt;
&lt;li&gt;Caches cannot be reused across regional processing boundaries or shared across organizations, so one prefix served from two regions counts twice.&lt;/li&gt;
&lt;li&gt;List prices change, so store the price you used next to each calculation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Workload audit checklist
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;List every prefix your service sends that meets the minimum cacheable length (1,024 tokens on GPT-5.6 and later), with its model and API.&lt;/li&gt;
&lt;li&gt;Confirm which prefixes run on GPT-5.6 and later in the Responses API. Only those can use 12h.&lt;/li&gt;
&lt;li&gt;Move variable content, such as a timestamp, after the last breakpoint, and keep tools and output settings stable between requests.&lt;/li&gt;
&lt;li&gt;Record how often each prefix changes. A prefix that changes before each burst stays on 30m.&lt;/li&gt;
&lt;li&gt;Count G for each prefix in each 12-hour window from your logs.&lt;/li&gt;
&lt;li&gt;Check misses in your own &lt;code&gt;cached_tokens&lt;/code&gt; and &lt;code&gt;cache_write_tokens&lt;/code&gt;, and run Diagnostics on unexpected ones.&lt;/li&gt;
&lt;li&gt;Set 12h only on prefixes with G of 2 or more, and leave the rest on 30m.&lt;/li&gt;
&lt;li&gt;Replay or model a representative request trace with current prices before changing retention.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does OpenAI prompt caching store responses?
&lt;/h3&gt;

&lt;p&gt;No. The cache stores KV tensors for the reusable prefix at the start of the prompt, not the prompt tokens themselves. Caching does not change how the model generates output tokens.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is 12-hour prompt caching available in Chat Completions?
&lt;/h3&gt;

&lt;p&gt;OpenAI documents the 12-hour option for GPT-5.6 and later in the Responses API, through &lt;code&gt;prompt_cache_options.ttl&lt;/code&gt;. Plan on the Responses API if you want 12h.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does a higher cache hit rate mean lower cost?
&lt;/h3&gt;

&lt;p&gt;Not always on GPT-5.6 and later, because the 12h write costs 2.5x input instead of 1.25x. In modeled Scenario D, both options read the prefix on 90.0% of requests and 12h costs 36% more. In Scenario E, 12h raises the hit rate from 90.0% to 95.0% and still costs slightly more. Compare cost per prefix per window instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should we set 12h on every request?
&lt;/h3&gt;

&lt;p&gt;No. Set it only on stable prefixes that come back after two or more gaps longer than 30 minutes inside a 12-hour window. For steady traffic, or a prefix that changes often, the default 30m costs less.&lt;/p&gt;




&lt;p&gt;Before you change retention on any prefix, pull a representative trace from your request logs, run it through the replay model with current prices, and check the result against the billing rules in &lt;a href="https://developers.openai.com/api/docs/guides/prompt-caching" rel="noopener noreferrer"&gt;OpenAI's prompt caching guide&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>performance</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>What Are GitHub Actions Lockfiles, and Do They Replace SHA Pinning?</title>
      <dc:creator>Alisa Hester</dc:creator>
      <pubDate>Fri, 23 Oct 2026 23:52:51 +0000</pubDate>
      <link>https://dev.to/alisa_hester_1/what-are-github-actions-lockfiles-and-do-they-replace-sha-pinning-3kg0</link>
      <guid>https://dev.to/alisa_hester_1/what-are-github-actions-lockfiles-and-do-they-replace-sha-pinning-3kg0</guid>
      <description>&lt;p&gt;Your SDK release workflow runs when someone pushes a version tag. Its steps read &lt;code&gt;actions/checkout@v4&lt;/code&gt;, &lt;code&gt;actions/setup-node@v4&lt;/code&gt;, and &lt;code&gt;actions/attest-build-provenance@v1&lt;/code&gt;. Each &lt;code&gt;@v4&lt;/code&gt; or &lt;code&gt;@v1&lt;/code&gt; is a git ref in another team's repository. The commit it points to can change after your team reviewed the workflow, and nothing in your repository records which commit was reviewed.&lt;/p&gt;

&lt;p&gt;The usual fix is to pin every action to a full commit SHA, as the &lt;a href="https://architecture.lullabot.com/adr/20260505-pin-github-actions-to-hash/" rel="noopener noreferrer"&gt;Lullabot architecture decision record&lt;/a&gt; requires. Teams keep those pins current with Renovate, Dependabot, &lt;a href="https://github.com/suzuki-shunsuke/pinact" rel="noopener noreferrer"&gt;pinact&lt;/a&gt;, or &lt;a href="https://github.com/sethvargo/ratchet" rel="noopener noreferrer"&gt;ratchet&lt;/a&gt;, and &lt;a href="https://github.com/stacklok/frizbee" rel="noopener noreferrer"&gt;frizbee&lt;/a&gt; converts tags to SHAs. That fixes the lines you can see. &lt;a href="https://nesbitt.io/2025/12/06/github-actions-package-manager" rel="noopener noreferrer"&gt;Andrew Nesbitt pointed out&lt;/a&gt; the gap: a SHA pin locks only the outer action, and a helper action it calls by tag can still change.&lt;/p&gt;

&lt;p&gt;GitHub's workflow dependency lockfile moves pinning into one machine-generated file per repository. For each onboarded workflow, it records the ref, the resolved commit, and the source repository's identity, transitive actions included. For repositories that adopt it, GitHub says SHA pinning in workflow files is no longer necessary. The guarantee stops at known edges: the dependencies inside reusable workflows you call, container images pulled by tag, and the safety of the pinned code. Reviewers still read every lock diff. For one release workflow on github.com, a bounded pilot is a reasonable next step, unless you run GitHub Enterprise Server or depend mostly on reusable workflows you do not control.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A lockfile at &lt;code&gt;.github/workflows/actions.lock&lt;/code&gt; records each onboarded workflow's dependencies, their commits, and their source repositories' IDs, transitive actions included.&lt;/li&gt;
&lt;li&gt;For repositories that adopt a lockfile, GitHub says inline SHA pinning is no longer necessary. Keep SHAs wherever no lockfile reaches.&lt;/li&gt;
&lt;li&gt;Generating the file, onboarding a workflow, running &lt;code&gt;--verify&lt;/code&gt; in CI, and the Require lockfile policy are four separate controls. Only the policy blocks runs of workflows nobody onboarded.&lt;/li&gt;
&lt;li&gt;Your lockfile does not control the dependencies inside a reusable workflow you call, and container images pulled by tag are not locked.&lt;/li&gt;
&lt;li&gt;The feature and its pre-1.0 CLI are in preview, and GHES is not supported. Pilot one workflow before you require locking.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What changed, and what is still preview
&lt;/h2&gt;

&lt;p&gt;On October 14, 2026, GitHub &lt;a href="https://github.blog/changelog/2026-10-14-github-actions-workflow-lockfiles-in-public-preview/" rel="noopener noreferrer"&gt;put workflow lockfiles into public preview&lt;/a&gt; for GitHub Enterprise Cloud (including data residency), Team, and Free plans. Each of GitHub's lockfile pages, starting with the &lt;a href="https://docs.github.com/en/enterprise-cloud@latest/actions/concepts/security/workflow-dependency-lockfiles" rel="noopener noreferrer"&gt;concept page&lt;/a&gt;, says the feature is in public preview and subject to change.&lt;/p&gt;

&lt;p&gt;The announcement now explicitly lists Team and Free, with an editor's note correcting plan availability. The setup guide still labels the feature GitHub Enterprise Cloud. Use the updated announcement for preview eligibility, and confirm which policy controls are available for your target repository.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/github/gh-actions-lock" rel="noopener noreferrer"&gt;gh-actions-lock README&lt;/a&gt; says the tool is pre-1.0, that its file format, flags, and behavior may change between releases, and that GitHub Enterprise Server is not supported. The test examples below describe &lt;a href="https://github.com/github/gh-actions-lock/releases" rel="noopener noreferrer"&gt;v0.2.0&lt;/a&gt;, published October 12, 2026, as tested on October 15, 2026. As of October 23, 2026, the latest published release is &lt;a href="https://github.com/github/gh-actions-lock/releases/tag/v0.3.0" rel="noopener noreferrer"&gt;v0.3.0, released October 22 at 16:08 UTC&lt;/a&gt;. It changes local-action handling, adds container-image warnings, and tightens verification. The historical examples are retained unchanged; we have not rerun them with v0.3.0.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the lockfile records
&lt;/h2&gt;

&lt;p&gt;Each repository gets one lockfile at &lt;code&gt;.github/workflows/actions.lock&lt;/code&gt;. You can onboard workflows to it individually or all at once. The concept page says the file should never be edited by hand. The &lt;code&gt;gh actions-lock&lt;/code&gt; extension generates it, which &lt;a href="https://docs.github.com/en/enterprise-cloud@latest/actions/how-tos/secure-your-work/lock-workflow-dependencies" rel="noopener noreferrer"&gt;requires write access&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The file has two sections. &lt;code&gt;workflows&lt;/code&gt; lists the dependencies each onboarded workflow uses. &lt;code&gt;dependencies&lt;/code&gt; holds one entry per dependency, with three kinds of field:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ref&lt;/code&gt; is the tag or branch the workflow names.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;commit&lt;/code&gt; is the code that will run, written &lt;code&gt;sha1-...&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;owner_id&lt;/code&gt; and &lt;code&gt;repo_id&lt;/code&gt; let GitHub confirm at run time that the repository behind the name is still the one you locked.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Transitive actions appear as a &lt;code&gt;uses:&lt;/code&gt; list under the parent entry, as in the &lt;a href="https://github.com/github/gh-actions-lock/blob/main/.github/workflows/actions.lock" rel="noopener noreferrer"&gt;lockfile in the extension's own repository&lt;/a&gt;. That file has no integrity or content hash field, only the commit (our reading; GitHub does not state it).&lt;/p&gt;

&lt;p&gt;Once a workflow is onboarded, every action it uses must be in the lockfile. The README describes job-start checks of source-repository identity, the pinned commit, and full-version tags. The released CLI distinguishes a rename or transfer that preserves the repository ID, which produces a warning, from a different repository behind the name, which produces a blocking finding. Its &lt;a href="https://github.com/github/gh-actions-lock/blob/v0.2.0/README.md" rel="noopener noreferrer"&gt;v0.2.0 README&lt;/a&gt; says a failed check blocks the run. This describes the documented checks; malformed files and CLI/runtime differences need the separate caveats below.&lt;/p&gt;

&lt;h2&gt;
  
  
  A test run on one release workflow
&lt;/h2&gt;

&lt;p&gt;We onboarded the release workflow from the opening in a test run with gh-actions-lock v0.2.0 (gh 2.102.0, Linux). We ran the release binary directly. We did not exercise hosted run-time enforcement, the Require lockfile policy, or Dependabot. The original &lt;code&gt;release.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;release&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;v*'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
  &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
  &lt;span class="na"&gt;attestations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;publish&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;22&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci &amp;amp;&amp;amp; npm pack&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/attest-build-provenance@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;subject-path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*.tgz'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub's how-to installs the extension with &lt;code&gt;gh extension install github/gh-actions-lock&lt;/code&gt;. Onboarding one workflow takes its path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh actions-lock .github/workflows/release.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It exited 0 and printed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scanning 1 workflow
Planning pins
Pinned 3 actions (+ 2 transitive) across 1 workflow
  actions/checkout@v4.4.0 (11d5960)
    └─ .github/workflows/release.yml
  actions/setup-node@v4.4.0 (49933ea)
    └─ .github/workflows/release.yml
  actions/attest-build-provenance@v1.4.4 (ef24412)
    └─ .github/workflows/release.yml
Transitive dependencies (from composite actions):
  actions/attest-build-provenance@36fa7d0 via actions/attest-build-provenance@v1.4.4
  actions/attest@67422f5 via actions/attest-build-provenance@v1.4.4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also rewrote the workflow: &lt;code&gt;@v4&lt;/code&gt; became &lt;code&gt;@v4.4.0&lt;/code&gt;, and &lt;code&gt;@v1&lt;/code&gt; became &lt;code&gt;@v1.4.4&lt;/code&gt;. The two transitive entries come from inside the composite &lt;code&gt;actions/attest-build-provenance&lt;/code&gt;; no workflow line names them. The excerpt below is machine-generated output from the test run, copied verbatim. Each &lt;code&gt;# …&lt;/code&gt; marks lines we cut.&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="c1"&gt;# This file is machine-generated by `gh actions-lock`.&lt;/span&gt;
&lt;span class="c1"&gt;# Do not edit by hand; run `gh actions-lock` to update.&lt;/span&gt;
&lt;span class="c1"&gt;# Docs: https://gh.io/actions-lockfile&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;v0.0.3'&lt;/span&gt;
&lt;span class="na"&gt;workflows&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.github/workflows/release.yml'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;actions/attest-build-provenance@v1.4.4'&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;actions/checkout@v4.4.0'&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;actions/setup-node@v4.4.0'&lt;/span&gt;
&lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# …&lt;/span&gt;
    &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;actions/attest-build-provenance@v1.4.4'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;v1.4.4'&lt;/span&gt;
        &lt;span class="na"&gt;commit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sha1-ef244123eb79f2f7a7e75d99086184180e6d0018'&lt;/span&gt;
        &lt;span class="na"&gt;owner_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;44036562&lt;/span&gt;
        &lt;span class="na"&gt;repo_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;760702757&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;actions/attest-build-provenance@36fa7d009e22618ca7cd599486979b8150596c74'&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;actions/attest@67422f5511b7ff725f4dbd6fb9bd2cd925c65a8d'&lt;/span&gt;
&lt;span class="err"&gt;    &lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;actions/attest@67422f5511b7ff725f4dbd6fb9bd2cd925c65a8d'&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;v1.4.1'&lt;/span&gt;
        &lt;span class="na"&gt;commit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sha1-67422f5511b7ff725f4dbd6fb9bd2cd925c65a8d'&lt;/span&gt;
        &lt;span class="na"&gt;owner_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;44036562&lt;/span&gt;
        &lt;span class="na"&gt;repo_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;760701061&lt;/span&gt;
    &lt;span class="c1"&gt;# …&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the lockfile committed, &lt;code&gt;gh actions-lock --verify&lt;/code&gt; printed "All 1 workflow valid" and exited 0. We then appended &lt;code&gt;- uses: actions/upload-artifact@v4&lt;/code&gt; without relocking, and &lt;code&gt;--verify&lt;/code&gt; exited 1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scanning 1 workflow
1 of 1 workflow failed
✗ Not pinned actions/upload-artifact@v4
  used in workflow but not pinned in lockfile (actions/upload-artifact@v4)
  see: https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions#using-third-party-actions
  ↳ .github/workflows/release.yml
Re-run without --verify to apply fixes.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The offline &lt;code&gt;--verify-local&lt;/code&gt; also exited 1, with "1 of 1 workflow failed: 1 not-pinned." Its &lt;a href="https://github.com/github/gh-actions-lock/tree/main/cmd/gh-actions-lock" rel="noopener noreferrer"&gt;flag help&lt;/a&gt; says it only checks that every action has an entry, not that the entries are trustworthy. That makes it a fit for a pre-commit hook, while GitHub's docs use &lt;code&gt;--verify&lt;/code&gt; in CI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Direct, transitive, and reusable workflows: where the boundary sits
&lt;/h2&gt;

&lt;p&gt;The concept page draws the boundary at the repository: its lockfile covers its onboarded workflows and transitive actions, but the actions inside a called reusable workflow belong to the defining repository's lockfile.&lt;/p&gt;

&lt;p&gt;Your release workflow's direct steps land in your file, and so do the actions inside its composite actions. The dependencies inside a reusable workflow it calls do not. The lockfile of the repository that defines that reusable workflow covers them, if that repository has one.&lt;/p&gt;

&lt;p&gt;Our test matched the dependency boundary but recorded less than the concept page describes. That page says the lockfile pins the reusable workflows your workflows depend on, too. We onboarded a workflow whose only dependency was a call to &lt;code&gt;slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0&lt;/code&gt;. The tool reported "All 1 workflow valid" and left the file unchanged, and the caller's lockfile recorded &lt;code&gt;'.github/workflows/release.yml': []&lt;/code&gt;, with no entry for the call. The &lt;a href="https://docs.github.com/en/enterprise-cloud@latest/actions/how-tos/secure-your-work/lock-workflow-dependencies" rel="noopener noreferrer"&gt;how-to&lt;/a&gt; recommends onboarding repositories that hold shared workflows first.&lt;/p&gt;

&lt;p&gt;In v0.2.0, resolvable same-repository local &lt;code&gt;uses: ./…&lt;/code&gt; actions are rewritten to &lt;code&gt;uses: $/…&lt;/code&gt;, which always resolves to the running commit, though that release's README also lists local-path actions as a short-term gap. The &lt;a href="https://github.com/github/gh-actions-lock/blob/v0.3.0/README.md" rel="noopener noreferrer"&gt;v0.3.0 README&lt;/a&gt; says local ./ actions are never followed or locked. A local action that uses remote actions now blocks fix and verify by default: explicitly migrate it with --migrate-local-actions, or accept warning-only treatment with --ignore-local-actions. The latter leaves that local-action dependency path unlocked. The linked concept and setup pages do not explain container-image coverage. The &lt;a href="https://github.com/github/gh-actions-lock/blob/main/internal/workflowfile/workflowfile.go" rel="noopener noreferrer"&gt;CLI source&lt;/a&gt; warns that a &lt;code&gt;docker://image:tag&lt;/code&gt; step falls outside lockfile coverage because image tags can move.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do lockfiles replace inline SHA pinning?
&lt;/h2&gt;

&lt;p&gt;For repositories that adopt one, GitHub says yes. The concept page says SHA pinning in workflow files "is no longer necessary." The &lt;a href="https://docs.github.com/en/enterprise-cloud@latest/actions/reference/security/secure-use" rel="noopener noreferrer"&gt;secure use reference&lt;/a&gt; keeps full-length SHA pinning as the guidance for repositories that cannot adopt a lockfile.&lt;/p&gt;

&lt;p&gt;The condition applies per workflow. Keep full-length SHA pins on workflows you have not onboarded and on remote job-level reusable-workflow calls in v0.2.0, even when the callee repository has its own lockfile. That callee lockfile governs the actions inside the called workflow, not the caller's job-level ref. A &lt;a href="https://github.com/github/gh-actions-lock/issues/129#issuecomment-6195549413" rel="noopener noreferrer"&gt;GitHub maintainer confirms that job-level calls are not pinned&lt;/a&gt;. Keep separate image-digest pins on &lt;code&gt;docker://&lt;/code&gt; dependencies.&lt;/p&gt;

&lt;p&gt;The SHA policy keeps working. &lt;a href="https://docs.github.com/en/enterprise-cloud@latest/actions/how-tos/administer/control-workflow-execution" rel="noopener noreferrer"&gt;GitHub's page on governing workflow execution&lt;/a&gt; says you can leave "Require actions to be pinned to a full-length commit SHA" enabled. For locked dependencies, GitHub checks the locked commit. During a staged rollout, the policy still enforces SHA pinning for workflows that have not adopted the lockfile.&lt;/p&gt;

&lt;p&gt;Onboarding does change existing pins. In our test, &lt;code&gt;actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0&lt;/code&gt; became &lt;code&gt;actions/checkout@v4.4.0&lt;/code&gt; on a default run, with the commit moved into the lockfile. With &lt;code&gt;--no-narrow&lt;/code&gt;, the SHA stayed in the workflow file, which is the fix a maintainer gave in &lt;a href="https://github.com/github/gh-actions-lock/issues/130" rel="noopener noreferrer"&gt;issue #130&lt;/a&gt;. If your organization allows only selected actions, the how-to warns that allowlist entries pinned to a tag stop matching. Use a wildcard or the full commit SHA instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inline SHA pinning and a lockfile, side by side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Inline full-length SHA pinning&lt;/th&gt;
&lt;th&gt;Lockfile (onboarded workflow)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Where the pin lives&lt;/td&gt;
&lt;td&gt;Each &lt;code&gt;uses:&lt;/code&gt; line in each workflow file&lt;/td&gt;
&lt;td&gt;One &lt;code&gt;actions.lock&lt;/code&gt; per repository; workflow lines carry a version tag unless you use &lt;code&gt;--no-narrow&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transitive actions&lt;/td&gt;
&lt;td&gt;Whatever the composite action's author wrote&lt;/td&gt;
&lt;td&gt;Recorded under the parent entry, except inside reusable workflow calls&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repository identity&lt;/td&gt;
&lt;td&gt;Not recorded&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;owner_id&lt;/code&gt; and &lt;code&gt;repo_id&lt;/code&gt;, checked at job start&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What a reviewer sees&lt;/td&gt;
&lt;td&gt;A changed SHA on each edited line&lt;/td&gt;
&lt;td&gt;One diff with ref, commit, IDs, and transitive edges&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enforcement&lt;/td&gt;
&lt;td&gt;Full-length SHA policy&lt;/td&gt;
&lt;td&gt;Job-start checks, &lt;code&gt;--verify&lt;/code&gt; as a required check, Require lockfile policy, SHA policy on the locked commit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Update automation&lt;/td&gt;
&lt;td&gt;Renovate, Dependabot, pinact, or ratchet&lt;/td&gt;
&lt;td&gt;Dependabot regenerates lock entries; Renovate documents experimental regeneration of an existing lockfile. Neither onboards new workflows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Container images by tag&lt;/td&gt;
&lt;td&gt;Not covered by action pins&lt;/td&gt;
&lt;td&gt;Not locked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Availability&lt;/td&gt;
&lt;td&gt;GitHub's guidance for repositories that cannot adopt a lockfile&lt;/td&gt;
&lt;td&gt;Public preview on Enterprise Cloud (including data residency), Team, and Free per the updated announcement; no GHES. Confirm target-repository policy availability&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  File, onboarding, CI check, and policy are four different controls
&lt;/h2&gt;

&lt;p&gt;A lockfile in the repository does not prove that every workflow is governed.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Control&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Availability&lt;/th&gt;
&lt;th&gt;On failure&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Generating the file&lt;/td&gt;
&lt;td&gt;Pins each resolvable action and writes the lockfile&lt;/td&gt;
&lt;td&gt;People with write access, through the gh extension&lt;/td&gt;
&lt;td&gt;All or nothing: if it hits an error it cannot fix, it changes no workflow and no lockfile entry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Onboarding a workflow&lt;/td&gt;
&lt;td&gt;Adds it to &lt;code&gt;workflows&lt;/code&gt;; every dependency must then be locked&lt;/td&gt;
&lt;td&gt;Individually or all at once&lt;/td&gt;
&lt;td&gt;Documented job-start checks block failures; malformed-file fail-closed behavior requires the Require lockfile policy. We did not test hosted enforcement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;--verify&lt;/code&gt; in CI&lt;/td&gt;
&lt;td&gt;Read-only CLI validation for coverage and pin checks; use it as a required check, not as proof of identical hosted enforcement&lt;/td&gt;
&lt;td&gt;Any CI job that runs the CLI&lt;/td&gt;
&lt;td&gt;Exits 1 (our test); blocks merges only as a required check&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Require lockfile policy&lt;/td&gt;
&lt;td&gt;Blocks runs of targeted workflows that use actions but are not onboarded&lt;/td&gt;
&lt;td&gt;Public repositories, and private repositories on Team or Enterprise; Evaluate mode only on GitHub Enterprise&lt;/td&gt;
&lt;td&gt;Blocks runs in Active mode; Evaluate reports to Policy insights; we did not test this&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Until the policy targets it, a workflow nobody onboarded runs with whatever refs its file contains. The &lt;a href="https://docs.github.com/en/enterprise-cloud@latest/actions/how-tos/administer/enforce-workflow-lockfiles" rel="noopener noreferrer"&gt;enforcement guide&lt;/a&gt; covers both the policy rollout and the verify job. The workflow-policy page lists public repositories and private repositories on Team or Enterprise; private Free repositories are not listed.&lt;/p&gt;

&lt;p&gt;Malformed lockfiles are a separate case. A &lt;a href="https://github.com/github/gh-actions-lock/issues/125#issuecomment-6195115498" rel="noopener noreferrer"&gt;GitHub maintainer says the Require lockfile policy supplies fail-closed behavior&lt;/a&gt; when the file cannot be reliably read. Onboarding and a CLI check alone are not equivalent to an active runtime policy.&lt;/p&gt;

&lt;p&gt;CLI/runtime parity also needs a version boundary. In &lt;a href="https://github.com/github/gh-actions-lock/issues/174#issuecomment-6206071325" rel="noopener noreferrer"&gt;issue #174, the maintainer reported a backend bypass repair on October 16&lt;/a&gt;. On &lt;a href="https://github.com/github/gh-actions-lock/issues/174#issuecomment-6326391046" rel="noopener noreferrer"&gt;October 21, the maintainer said the CLI fix was on main and planned for the next release&lt;/a&gt;. The historical &lt;a href="https://github.com/github/gh-actions-lock/blob/v0.2.0/internal/pipeline/parity.go" rel="noopener noreferrer"&gt;v0.2.0 verification code&lt;/a&gt; has the full-SHA ancestry-check limitation. That repair has now shipped: v0.3.0 rejects fork-only impostor commits and checks locked commits against the history of their recorded tag or branch. Its release notes also describe incomplete-lockfile failures for older lock graphs, exact-tag/commit mismatch errors, failures when GitHub cannot answer a pin check, and refusal to write cyclic or incomplete lock graphs. Follow the release-specific repair instructions and review the resulting diff before adopting it. We checked the release notes and &lt;a href="https://github.com/github/gh-actions-lock/blob/v0.3.0/internal/pipeline/parity.go" rel="noopener noreferrer"&gt;v0.3.0 verification source&lt;/a&gt;; we did not reproduce hosted enforcement or rerun the CLI with this version.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a reviewer approves on a lock update
&lt;/h2&gt;

&lt;p&gt;The enforcement guide sets the condition: "A lockfile is only a security control if someone reads the diff." On each lock pull request, check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each changed &lt;code&gt;commit&lt;/code&gt;, which is the code your release will run. The how-to treats it like any dependency version bump.&lt;/li&gt;
&lt;li&gt;Each changed &lt;code&gt;owner_id&lt;/code&gt; or &lt;code&gt;repo_id&lt;/code&gt;, which the how-to calls rare and worth investigating before you merge.&lt;/li&gt;
&lt;li&gt;New entries in a &lt;code&gt;uses:&lt;/code&gt; list, which are transitive actions nobody typed into a workflow.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;workflows&lt;/code&gt; section, to see whether a workflow dropped out.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A normal run trusts refs such as &lt;code&gt;main&lt;/code&gt; or &lt;code&gt;v3&lt;/code&gt; from the lockfile and does not re-resolve them. If upstream force-pushed or deleted a locked commit, &lt;code&gt;--relock&lt;/code&gt; errors. Accepting the new commit takes &lt;code&gt;gh actions-lock --relock --accept-moved&lt;/code&gt;, and that change deserves its own review.&lt;/p&gt;

&lt;p&gt;For onboarded workflows, Dependabot regenerates the matching lockfile entry in the same pull request as the version update. It does not onboard workflows; it reports that onboarding is required. Beyond &lt;a href="https://docs.github.com/en/code-security/how-tos/secure-your-supply-chain/secure-your-dependencies/auto-update-actions" rel="noopener noreferrer"&gt;having Dependabot manage GitHub Actions dependencies&lt;/a&gt;, no configuration is needed. GitHub notes that this maintains coverage rather than extending it. &lt;a href="https://docs.renovatebot.com/modules/manager/github-actions/" rel="noopener noreferrer"&gt;Renovate also documents experimental regeneration of an existing actions.lock&lt;/a&gt; after action updates; workflows not yet onboarded are skipped. Neither tool onboards new workflows automatically. The cited guides do not specify a minimum Dependabot version.&lt;/p&gt;

&lt;h2&gt;
  
  
  The case for staying with SHA pins and Dependabot
&lt;/h2&gt;

&lt;p&gt;The objection: "We already pin to SHAs and have Dependabot; this is a preview that adds another file and another failure mode." SHA pins plus Dependabot already fix and update every direct reference. The preview adds a pre-1.0 CLI, workflow rewrites, allowlist changes, CodeQL alerts, and early policy bugs. In the &lt;a href="https://github.com/orgs/community/discussions/194494" rel="noopener noreferrer"&gt;proposal thread&lt;/a&gt;, one commenter wrote in May 2026 that they would not use it instead of SHA pinning until it worked with Dependabot or Renovate.&lt;/p&gt;

&lt;p&gt;For direct references the objection holds, because an inline SHA and a lock entry fix the same commit. Our inference from the documented behavior is that lockfiles address three gaps left by inline SHAs, within the coverage and enforcement limits above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transitive actions.&lt;/li&gt;
&lt;li&gt;Repository-name reuse that points to a different repository identity.&lt;/li&gt;
&lt;li&gt;A run-time check that covered dependencies are locked, with the active Require lockfile policy supplying fail-closed behavior for malformed files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dependabot now regenerates lock entries, and Renovate documents experimental regeneration for existing lockfiles, which addresses the update-integration objection. &lt;code&gt;--no-narrow&lt;/code&gt; keeps SHAs in the workflow file. The pre-1.0 CLI, allowlist changes, and CodeQL alerts still argue for testing on one workflow first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Early-adopter reports
&lt;/h2&gt;

&lt;p&gt;These come from the issue tracker, not from GitHub's documented behavior.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/github/gh-actions-lock/issues/130" rel="noopener noreferrer"&gt;Issue #130&lt;/a&gt; (closed): fix mode rewrote commit pins into tags. The maintainer pointed to &lt;code&gt;--no-narrow&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/github/gh-actions-lock/issues/131" rel="noopener noreferrer"&gt;Issue #131&lt;/a&gt; (open): after a lockfile rewrite, CodeQL flags the new tag references as unpinned.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/github/gh-actions-lock/issues/168" rel="noopener noreferrer"&gt;Issue #168&lt;/a&gt; (closed October 21): the report describes how a locked Docker container action, or a &lt;code&gt;docker://&lt;/code&gt; step inside a locked composite action, still pulls the image its &lt;code&gt;action.yml&lt;/code&gt; names, usually by tag. Moving that tag changes the image without a lockfile diff. v0.3.0 now emits non-blocking container-image warnings for docker:// steps, job containers and services, and remote Docker actions when images lack a SHA-256 digest, including expression-selected images. Those warnings do not pin images. The no-warning observation is the issue author's historical report, not a description of the latest release.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/github/gh-actions-lock/issues/169" rel="noopener noreferrer"&gt;Issue #169&lt;/a&gt; (closed): with the SHA-pinning policy on, version-tagged actions run through a reusable workflow were rejected despite a valid lockfile. A GitHub maintainer said a backend fix should address it, and the issue is closed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What a lockfile does not establish
&lt;/h2&gt;

&lt;p&gt;The changelog says locking "does not determine whether the dependency itself is safe," so review dependencies before you lock them. The secure use reference still says to audit the source code of every action you use.&lt;/p&gt;

&lt;p&gt;The remaining boundaries combine the CLI's documented limits with our inference about what a commit lock can govern. The lock records commits without a content hash, while the earlier third-party &lt;a href="https://github.com/gjtorikian/gh-actions-lockfile" rel="noopener noreferrer"&gt;gh-actions-lockfile&lt;/a&gt; records integrity hashes. Code an action downloads at run time is outside the lock, a point raised in the proposal thread. So are images pulled by tag, the runner image, workflow permissions, and secret handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist: pilot one SDK or API release workflow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Inventory every &lt;code&gt;uses:&lt;/code&gt; line: direct and composite actions, reusable workflows and their repositories, local &lt;code&gt;./&lt;/code&gt; actions, and image-based actions.&lt;/li&gt;
&lt;li&gt;Onboard the repositories that hold reusable workflows you control first. Keep the caller's remote job-level reusable-workflow refs pinned to full-length SHAs even when those repositories have lockfiles. Record repositories you do not control as out of scope.&lt;/li&gt;
&lt;li&gt;Onboard only the release workflow. Add &lt;code&gt;--no-narrow&lt;/code&gt; if you want SHAs kept in the file. We expect that to make the pilot easier to back out, but we did not test a rollback.&lt;/li&gt;
&lt;li&gt;Review every &lt;code&gt;commit&lt;/code&gt;, &lt;code&gt;owner_id&lt;/code&gt;, &lt;code&gt;repo_id&lt;/code&gt;, and transitive entry in the generated lockfile.&lt;/li&gt;
&lt;li&gt;Move tag-based allowlist entries to a wildcard or full SHA, and decide how to triage CodeQL alerts.&lt;/li&gt;
&lt;li&gt;Require CODEOWNERS review of &lt;code&gt;.github/workflows/actions.lock&lt;/code&gt;, with a named owner.&lt;/li&gt;
&lt;li&gt;Make a &lt;code&gt;gh actions-lock --verify&lt;/code&gt; CI job a required check. For pre-commit, the enforcement guide uses &lt;code&gt;gh actions-lock --verify-local --no-fix&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Keep Dependabot on GitHub Actions dependencies, and keep the full-length SHA policy enabled.&lt;/li&gt;
&lt;li&gt;On GitHub Enterprise, run the Require lockfile policy in Evaluate mode, scoped to this workflow. GitHub advises switching to Active only after you have dependable lockfile-maintenance processes for every targeted workflow.&lt;/li&gt;
&lt;li&gt;Exit test over the next few releases: each release ran onboarded, &lt;code&gt;--verify&lt;/code&gt; failed only on real drift, every lock diff had a named reviewer, and a Dependabot update arrived with its lock entry.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Defer the pilot if any of these apply:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You run GitHub Enterprise Server.&lt;/li&gt;
&lt;li&gt;You depend mostly on reusable workflows you do not control.&lt;/li&gt;
&lt;li&gt;You rely on Docker-image-based actions that you need pinned.&lt;/li&gt;
&lt;li&gt;Nobody owns lock diffs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you defer, keep SHA pins with Dependabot and revisit after the CLI reaches 1.0.&lt;/p&gt;

&lt;p&gt;To start, follow GitHub's guide to &lt;a href="https://docs.github.com/en/enterprise-cloud@latest/actions/how-tos/secure-your-work/lock-workflow-dependencies" rel="noopener noreferrer"&gt;locking action dependencies in a repository's workflows&lt;/a&gt;, then the guide to &lt;a href="https://docs.github.com/en/enterprise-cloud@latest/actions/how-tos/administer/enforce-workflow-lockfiles" rel="noopener noreferrer"&gt;enforcing lockfiles&lt;/a&gt; when you are ready for the policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Will a lockfile break my existing SHA-pinned actions?
&lt;/h3&gt;

&lt;p&gt;The commit you pinned stays. A default run rewrites the line to its version tag and moves the commit into the lockfile; &lt;code&gt;--no-narrow&lt;/code&gt; keeps the SHA in the workflow. Tag-based allowlist entries stop matching, and CodeQL may flag the rewritten tags.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I keep the full-length commit SHA policy enabled?
&lt;/h3&gt;

&lt;p&gt;Yes. For locked dependencies, GitHub evaluates the policy against the locked commit. Workflows that are not onboarded still need full-length SHAs in the workflow file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Will Dependabot continue to update my actions?
&lt;/h3&gt;

&lt;p&gt;For onboarded workflows, Dependabot regenerates the matching lockfile entry in its version-update pull request. It does not onboard new workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does my lockfile cover the reusable workflows I call?
&lt;/h3&gt;

&lt;p&gt;Not their dependencies. The actions inside a reusable workflow are covered by the lockfile of the repository where it is defined, so GitHub recommends onboarding shared-workflow repositories first. GitHub's concept page says reusable workflows themselves are pinned, but in our v0.2.0 test the caller's lockfile recorded no entry for the call. Keep remote job-level calls SHA-pinned even when the called repository has its own lockfile.&lt;/p&gt;

</description>
      <category>githubactions</category>
      <category>security</category>
      <category>devops</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Claude Code for technical writers: what works and what breaks</title>
      <dc:creator>Alisa Hester</dc:creator>
      <pubDate>Fri, 23 Oct 2026 23:52:45 +0000</pubDate>
      <link>https://dev.to/alisa_hester_1/claude-code-for-technical-writers-what-works-and-what-breaks-3h53</link>
      <guid>https://dev.to/alisa_hester_1/claude-code-for-technical-writers-what-works-and-what-breaks-3h53</guid>
      <description>&lt;p&gt;Claude Code can turn a Git diff, or your description of a change, into a first draft. Without setup, though, nothing holds that draft to your style guide or term list, and it writes from instructions built for engineering work. Then engineering merges an API change while you are on vacation or deep in another project, and without a maintenance workflow the docs can wait until you get back. EkLine connects repository-change detection, scheduled maintenance, and reviewable docs updates for a team-owned accuracy process.&lt;/p&gt;

&lt;p&gt;Most of that is fixable today. Claude Code reads project instructions, path-scoped rules, skills, a custom output style, and hooks. Set up together, they give Claude your reader, your terms, and a lint check after matching successful Write or Edit calls while the hook is enabled. Features such as scheduled tasks (formerly routines), Code Review, and shared project instructions partly close each of the old capability gaps. What they leave in place is a dependency on one person: auto-memory corrections stay on one machine unless promoted into shared files, scheduled tasks run as one person's account, and Claude's own review never blocks a merge.&lt;/p&gt;

&lt;p&gt;The full claim review used first-party documentation checked on October 21, 2026. Selected setup instructions were checked again on October 23, 2026. The &lt;a href="https://code.claude.com/docs/en/changelog" rel="noopener noreferrer"&gt;Claude Code changelog&lt;/a&gt; tracks CLI releases; cloud-service behavior is documented separately. These are documentation-based examples, not an end-to-end test in a production repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Name your reader and terms in a short CLAUDE.md; scope docs rules to docs files.&lt;/li&gt;
&lt;li&gt;Add an output style for user-facing pages and an enabled lint hook for successful Write or Edit calls.&lt;/li&gt;
&lt;li&gt;Scheduled tasks run as one person, and Code Review never blocks a merge.&lt;/li&gt;
&lt;li&gt;Stop building your own pipeline when the docs must keep running while you are away.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Claude Code writes like an engineer
&lt;/h2&gt;

&lt;p&gt;The engineer voice is the default by design. With no output style selected, Claude &lt;a href="https://code.claude.com/docs/en/output-styles" rel="noopener noreferrer"&gt;uses Claude Code's default software-engineering instructions.&lt;/a&gt; Unless you tell it who the reader is, it works from instructions written for engineering work, not for your reader.&lt;/p&gt;

&lt;p&gt;Your instructions also compete for attention. Discovered project and ancestor instruction files load at session start, along with the auto-memory index (up to its first 200 lines or 25 KB), but &lt;a href="https://code.claude.com/docs/en/memory" rel="noopener noreferrer"&gt;Claude treats them as "context, not enforced configuration."&lt;/a&gt; The same page advises keeping each CLAUDE.md under 200 lines, because longer files reduce adherence. Pasting your full style guide in works against you.&lt;/p&gt;

&lt;p&gt;So split the work: judgment calls (who the reader is, what a page is for) go in short instructions, and mechanical rules go to something deterministic. Mintlify's Ethan Palm makes the same recommendation in &lt;a href="https://www.mintlify.com/blog/how-mintlify-uses-claude-code-as-a-technical-writing-assistant" rel="noopener noreferrer"&gt;his technical-writing workflow&lt;/a&gt;: put checkable conventions in a linter or lifecycle hook rather than spending instruction-file space on them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you start
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Claude Code running in the repository that holds your docs.&lt;/li&gt;
&lt;li&gt;A written style guide and term list.&lt;/li&gt;
&lt;li&gt;Vale configured for your style guide, plus &lt;code&gt;jq&lt;/code&gt;, which the &lt;a href="https://code.claude.com/docs/en/hooks-guide" rel="noopener noreferrer"&gt;hooks guide&lt;/a&gt; uses to read the edited file's path.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each example below is adapted from a documented snippet. Test it in your repository before you rely on 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffbwijwlffov50kotqz5u.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%2Ffbwijwlffov50kotqz5u.png" alt="Five stacked layers of a Claude Code docs setup: CLAUDE\.md, a path\-scoped rule, a skill, and an output style are advisory, so Claude may follow them; an enabled lint hook runs deterministically on matching successful Write or Edit calls" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Instructions guide Claude. In the diagram, "always runs" means an enabled hook on a matching successful Write or Edit call. It does not include edits made through Bash, disabled hooks, or failed tool calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set up Claude Code for documentation in five steps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Write a short CLAUDE.md that names the reader
&lt;/h3&gt;

&lt;p&gt;A project CLAUDE.md lives at &lt;code&gt;./CLAUDE.md&lt;/code&gt; or &lt;code&gt;./.claude/CLAUDE.md&lt;/code&gt;, and its instructions are &lt;a href="https://code.claude.com/docs/en/memory" rel="noopener noreferrer"&gt;"shared with your team through version control."&lt;/a&gt; Lead with the reader, then terminology, then a pointer to the full guide. Illustrative example: replace the reader, terms, and style-guide path with your own:&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="gh"&gt;# Documentation conventions&lt;/span&gt;

&lt;span class="gu"&gt;## Reader&lt;/span&gt;
Write for backend developers who already know HTTP and want to integrate an API.

&lt;span class="gu"&gt;## Terminology&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Use "API key" and never "API token"

&lt;span class="gu"&gt;## Style&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Follow docs/style-guide.md for anything not covered here.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Scope docs rules to docs files
&lt;/h3&gt;

&lt;p&gt;Rules in &lt;code&gt;.claude/rules/&lt;/code&gt; with a &lt;code&gt;paths&lt;/code&gt; field load only when Claude reads or edits a matching file, according to the &lt;a href="https://code.claude.com/docs/en/memory" rel="noopener noreferrer"&gt;memory documentation&lt;/a&gt;. This keeps unrelated file rules out of the initial context; matching rules still consume context when loaded. Example, adapted from the documented format:&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;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;docs/**/*.md"&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gh"&gt;# Docs page rules&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; One instruction per numbered step
&lt;span class="p"&gt;-&lt;/span&gt; Start each step with a verb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Package repeat checks as a skill
&lt;/h3&gt;

&lt;p&gt;A skill lives at &lt;code&gt;.claude/skills/terminology-check/SKILL.md&lt;/code&gt;. Claude &lt;a href="https://code.claude.com/docs/en/skills" rel="noopener noreferrer"&gt;loads it when relevant based on the description&lt;/a&gt;, or you run this example with &lt;code&gt;/terminology-check&lt;/code&gt;. Commit it so your team gets it too. Example, using the documented minimal format:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terminology-check&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;Checks a docs page against the term list. Use when a docs page is created or edited.&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

Compare every product term on the page with the Terminology section of CLAUDE.md and list each mismatch with its line.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plugins are the &lt;a href="https://code.claude.com/docs/en/features-overview" rel="noopener noreferrer"&gt;packaging layer for reusing the same setup across multiple repositories&lt;/a&gt;. In an October 8, 2026 &lt;a href="https://www.reddit.com/r/claudeskills/comments/1x0nmeh/i_packaged_asdste100_the_aerospace_simplified/" rel="noopener noreferrer"&gt;Reddit post, u/the-ai-scientist described an ASD-STE100-inspired writing plugin&lt;/a&gt;. This is its author's report, not an audit of standards compliance. EkLine's open-source &lt;a href="https://github.com/ekline-io/ekline-docs-skills" rel="noopener noreferrer"&gt;ekline-docs-skills&lt;/a&gt; plugin includes &lt;code&gt;style-guide&lt;/code&gt; and &lt;code&gt;terminology&lt;/code&gt; skills. For more options, see &lt;a href="https://ekline.io/blog/best-claude-skills-for-documentation-ranked" rel="noopener noreferrer"&gt;the 10 best Claude skills for documentation, ranked&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Add an output style for user-facing docs
&lt;/h3&gt;

&lt;p&gt;A custom output style &lt;a href="https://code.claude.com/docs/en/output-styles" rel="noopener noreferrer"&gt;can put Claude in a writing-assistant role&lt;/a&gt;. Save it in &lt;code&gt;.claude/output-styles/&lt;/code&gt;. Leave out &lt;code&gt;keep-coding-instructions&lt;/code&gt; when Claude won't be doing software engineering in that session. After creating or editing the style file, restart the terminal Claude Code session so it loads the change, then switch with &lt;code&gt;/output-style&lt;/code&gt;. Example, using the documented frontmatter:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;User docs&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;Writes and edits pages for the reader named in CLAUDE.md&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

You are editing user-facing documentation. Lead every page with the reader's task.
Explain what a feature changes for the reader before how it is built.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Put mechanical rules in a lint hook
&lt;/h3&gt;

&lt;p&gt;Hooks &lt;a href="https://code.claude.com/docs/en/hooks-guide" rel="noopener noreferrer"&gt;give you "deterministic control: certain actions always happen rather than relying on the LLM to choose to run them."&lt;/a&gt; Palm's Vale hook matches successful Write or Edit tool calls. Merge its &lt;code&gt;PostToolUse&lt;/code&gt; section into &lt;code&gt;.claude/settings.json&lt;/code&gt;; the file must already have valid settings JSON or be created with this object. This baseline does not feed lint failures back to Claude:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"PostToolUse"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Write|Edit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vale &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;$(jq -r '.tool_input.file_path // empty')&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a &lt;code&gt;PostToolUse&lt;/code&gt; hook, exit code 2 &lt;a href="https://code.claude.com/docs/en/hooks" rel="noopener noreferrer"&gt;"Shows stderr to Claude; the tool already ran."&lt;/a&gt; As published, the hook prints Vale's results to stdout, which Claude does not see. To hand failures to Claude, make the hook command send Vale's diagnostics to stderr and exit 2 only when lint fails; successful lint should exit 0. Either way, the edit is already saved. This matcher does not lint edits made through Bash or another tool, and it is not a CI merge gate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four gaps, checked against the current docs
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Gap&lt;/th&gt;
&lt;th&gt;What the docs show now&lt;/th&gt;
&lt;th&gt;What still holds&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No lasting style or terminology memory&lt;/td&gt;
&lt;td&gt;Project CLAUDE.md shared through git; managed CLAUDE.md deployed by IT/DevOps; path-scoped rules&lt;/td&gt;
&lt;td&gt;Memory is advisory; auto memory stays on one machine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No sense of the reader&lt;/td&gt;
&lt;td&gt;A custom output style can make Claude a writing assistant&lt;/td&gt;
&lt;td&gt;Nothing enforces the style&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No review gate&lt;/td&gt;
&lt;td&gt;Code Review comments on pull requests; a GitHub Action can run on pull requests&lt;/td&gt;
&lt;td&gt;Code Review never blocks a merge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nothing watching the repo&lt;/td&gt;
&lt;td&gt;Scheduled tasks run on a schedule or on merged pull requests&lt;/td&gt;
&lt;td&gt;Research preview; each scheduled task belongs to one person's account&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;What remains is about ownership.&lt;/p&gt;

&lt;p&gt;Shared instructions are covered, but what Claude learns from your corrections stays with you. Auto memory, the notes Claude saves from your corrections, &lt;a href="https://code.claude.com/docs/en/memory" rel="noopener noreferrer"&gt;"is machine-local,"&lt;/a&gt; so a term you fix on your laptop is unknown to a teammate's session. A subagent (a helper Claude starts with its own instructions) with &lt;code&gt;memory: project&lt;/code&gt; keeps its notes in a folder that is &lt;a href="https://code.claude.com/docs/en/sub-agents" rel="noopener noreferrer"&gt;"shareable via version control."&lt;/a&gt; That works when someone reviews and commits those notes.&lt;/p&gt;

&lt;p&gt;The output style gives Claude a reader, but &lt;a href="https://code.claude.com/docs/en/output-styles" rel="noopener noreferrer"&gt;"nothing enforces it,"&lt;/a&gt; and subagents other than a fork run their own system prompt, so the style does not reach them. You write the reader in by hand.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://code.claude.com/docs/en/code-review" rel="noopener noreferrer"&gt;Code Review "never approves or blocks the pull request,"&lt;/a&gt; and is available on Team and Enterprise plans for GitHub only. A &lt;code&gt;REVIEW.md&lt;/code&gt; file can add team rules, and &lt;code&gt;/code-review&lt;/code&gt; runs locally on any plan. The &lt;a href="https://code.claude.com/docs/en/github-actions" rel="noopener noreferrer"&gt;Claude Code GitHub Action&lt;/a&gt; costs GitHub Actions minutes plus API tokens per run (or subscription usage with an OAuth token), and its docs don't describe it as a merge gate.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://code.claude.com/docs/en/routines" rel="noopener noreferrer"&gt;Scheduled tasks, formerly routines,&lt;/a&gt; can trigger on merged pull requests, and Anthropic's own example is a weekly docs-drift task that opens update pull requests. They are in research preview and belong to the individual account that creates them. Their pull requests carry that person's connected GitHub identity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the setup fails, and how to recover
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Instructions fade. Trim CLAUDE.md and move page rules into a path-scoped rule.&lt;/li&gt;
&lt;li&gt;Voice slips inside a subagent. Write the reader into its prompt.&lt;/li&gt;
&lt;li&gt;Lint errors land after the page is saved. To block an action first, the memory docs point to a &lt;code&gt;PreToolUse&lt;/code&gt; hook.&lt;/li&gt;
&lt;li&gt;A scheduled task shows green but did nothing useful. The scheduled-tasks page says green reports a session without an infrastructure error, not that the requested work succeeded; GitHub webhook events beyond hourly caps are dropped. Read what it opened.&lt;/li&gt;
&lt;li&gt;A draft reads well and fails when followed. A technical writer posting as &lt;a href="https://news.ycombinator.com/item?id=49492810" rel="noopener noreferrer"&gt;cryzinger on Hacker News on August 29, 2026&lt;/a&gt; describes Claude-generated writeups that "fall apart when you actually try to follow the instructions they lay out." Have a developer walk every procedure before it ships.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When Claude Code alone is enough
&lt;/h2&gt;

&lt;p&gt;Claude Code now has project memory, skills, hooks, subagents, a GitHub Action, scheduled tasks, and Code Review, so a writer willing to maintain the setup can build much of a docs workflow themselves. Claude Code alone is a good fit when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One writer owns the docs and is around to drive most sessions.&lt;/li&gt;
&lt;li&gt;The docs live in one repository, or a plugin carries the setup across a few.&lt;/li&gt;
&lt;li&gt;Your team already runs a CI linter that can fail a pull request, so you don't need Claude to be the gate.&lt;/li&gt;
&lt;li&gt;You accept research-preview scheduled tasks acting as your account.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If that describes your team, the five steps are a starting setup. Confirm the hook loads, test a clean page and an intentional lint failure, and review its feedback before you rely on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The test: does the docs process run without you?
&lt;/h2&gt;

&lt;p&gt;Palm wants to "take a vacation without blocking docs ships for a week and a half." Ask four questions about your setup:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If an API change merges while you are out for two weeks, does a docs pull request appear under an identity your team owns?&lt;/li&gt;
&lt;li&gt;When a teammate corrects a term, does the next session on someone else's machine know about it?&lt;/li&gt;
&lt;li&gt;Can a docs problem fail a check and block the merge?&lt;/li&gt;
&lt;li&gt;Who maintains the hooks, rules, and scheduled tasks when you move to another project?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With Claude Code alone, the answers are "with scheduled tasks it runs as you; with the GitHub Action it uses the app and credentials your workflow configures," "only if someone commits it," "not through Code Review," and "you." When two or more of those answers worry you, look at tooling built for shared ownership.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where EkLine fits
&lt;/h2&gt;

&lt;p&gt;EkLine answers the same four questions for a team, and the writer still decides what ships.&lt;/p&gt;

&lt;h3&gt;
  
  
  Instructions and skills the whole team edits
&lt;/h3&gt;

&lt;p&gt;In Docs Agent, &lt;a href="https://docs.ekline.io/agent/custom-instructions/" rel="noopener noreferrer"&gt;custom instructions&lt;/a&gt; apply to every new session your organization starts after saving them, including new sessions started from GitHub, GitLab, or Slack, and any member can edit them. You set the reader there too. &lt;a href="https://docs.ekline.io/agent/custom-skills/" rel="noopener noreferrer"&gt;Custom skills&lt;/a&gt; are authored once and copied into every new session, run with the slash command shown for the saved skill. According to the &lt;a href="https://docs.ekline.io/changelog/" rel="noopener noreferrer"&gt;EkLine changelog&lt;/a&gt;, Docs Agent "remembers context about your repositories and conventions across sessions, so your whole team benefits from shared knowledge."&lt;/p&gt;

&lt;h3&gt;
  
  
  Watching the repository as a team
&lt;/h3&gt;

&lt;p&gt;With &lt;a href="https://docs.ekline.io/agent/automatic-pr-review/" rel="noopener noreferrer"&gt;automatic PR review&lt;/a&gt;, when a non-draft pull request opens or becomes ready on a selected GitHub repository, EkLine stays silent at low confidence, posts a suggestion at medium, and starts a docs-update flow at high confidence that can create a reviewable docs pull request. &lt;a href="https://docs.ekline.io/agent/scheduled-agents/" rel="noopener noreferrer"&gt;Scheduled Agents&lt;/a&gt; run templates such as Docs Drift Review and Code Sample Review hourly, daily, weekly, or monthly, and deliver a pull request, a Slack message, or both. According to the changelog, any organization member can create and manage them.&lt;/p&gt;

&lt;h3&gt;
  
  
  A check that can block the merge
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.ekline.io/reviewer/overview/" rel="noopener noreferrer"&gt;Docs Reviewer&lt;/a&gt; runs in CI on GitHub Actions, GitLab CI, and Bitbucket Pipelines. For GitHub Actions, the &lt;a href="https://docs.ekline.io/reviewer/style-guide-enforcement/" rel="noopener noreferrer"&gt;style-guide enforcement guide&lt;/a&gt; describes &lt;code&gt;fail_on_error: true&lt;/code&gt; plus a required branch-protection check. There is a current implementation discrepancy: the &lt;a href="https://github.com/ekline-io/ekline-github-action/blob/v6/action.yml" rel="noopener noreferrer"&gt;v6 manifest declares that input&lt;/a&gt;, but the &lt;a href="https://github.com/ekline-io/ekline-github-action/blob/v6/entrypoint.sh" rel="noopener noreferrer"&gt;v6 entrypoint does not pass it to reviewdog&lt;/a&gt;. Do not assume the setting alone blocks merges. In the version you deploy, verify that an intentional finding fails the job, then require that exact status check. GitLab and Bitbucket need their own CI configuration.&lt;/p&gt;

&lt;p&gt;Before: an API change merges during your vacation, and the docs wait for you. After: when automatic PR review detects clear impact on a monitored GitHub repository and the update flow completes, a docs pull request is available for your review when you get back. In an &lt;a href="https://ekline.io/case-study/how-socure-built-developer-experience-into-a-moat-with-ekline" rel="noopener noreferrer"&gt;EkLine case study&lt;/a&gt;, Socure's one technical writer merged 348 documentation updates in seven weeks.&lt;/p&gt;

&lt;p&gt;Know the limits: the automatic PR review assessment runs once per pull request, its confidence thresholds are fixed, and linked Jira or Linear tickets are not included.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What's the difference between CLAUDE.md and AGENTS.md?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://code.claude.com/docs/en/memory" rel="noopener noreferrer"&gt;Claude Code v2.1.277 or later can read AGENTS.md&lt;/a&gt; as project instructions when no CLAUDE.md, .claude/CLAUDE.md, or CLAUDE.local.md exists in the working directory or its ancestors. When those instruction files exist, the default loads them instead. Import AGENTS.md from CLAUDE.md, or use the Project instructions setting to load both. Direct AGENTS.md support also requires its built-in plugin to remain enabled; the first session after some older-version upgrades can still require an import or a new session.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I stop Claude Code from flattening my writing voice?
&lt;/h3&gt;

&lt;p&gt;Name your reader and voice in a short CLAUDE.md, use a custom output style for user-facing pages, and let a lint hook catch mechanical rules. Nothing enforces the style, so review diffs for voice before you merge.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which Claude Code skills help with docs?
&lt;/h3&gt;

&lt;p&gt;Start with one skill per repeat task, such as a terminology check, in &lt;code&gt;.claude/skills/&lt;/code&gt;. Shared options include an ASD-STE100 rule set and EkLine's open-source ekline-docs-skills plugin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it on one repository
&lt;/h2&gt;

&lt;p&gt;To check whether your docs process keeps running without you, &lt;a href="https://docs.ekline.io/agent/getting-started/" rel="noopener noreferrer"&gt;request Docs Agent access&lt;/a&gt; and test EkLine with one repository and one human reviewer. Docs Agent is on all plans, with access on request through &lt;a href="mailto:support@ekline.io"&gt;support@ekline.io&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>claude</category>
      <category>documentation</category>
      <category>ai</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>What Is Poppy? When Should Your API Support Customer Agents?</title>
      <dc:creator>Alisa Hester</dc:creator>
      <pubDate>Tue, 13 Oct 2026 01:00:10 +0000</pubDate>
      <link>https://dev.to/alisa_hester_1/what-is-poppy-when-should-your-api-support-customer-agents-3ip7</link>
      <guid>https://dev.to/alisa_hester_1/what-is-poppy-when-should-your-api-support-customer-agents-3ip7</guid>
      <description>&lt;p&gt;Poppy is the short name for the Personal Agent Protocol, which &lt;a href="https://personalagentprotocol.org/docs/overview" rel="noopener noreferrer"&gt;its overview defines as "an open protocol for how personal agents and companies work together."&lt;/a&gt; &lt;a href="https://sierra.ai/blog/introducing-personal-agent-protocol" rel="noopener noreferrer"&gt;Sierra and Meta are developing it with industry partners&lt;/a&gt;, and the &lt;a href="https://personalagentprotocol.org/docs/spec" rel="noopener noreferrer"&gt;specification is draft 0.1, updated October 9, 2026&lt;/a&gt;. As of October 12, 2026, the right call for most consumer-facing API platforms is a small, disposable experiment with no production rollout, while you build the pieces any customer-agent channel needs.&lt;/p&gt;

&lt;p&gt;By &lt;a href="https://sierra.ai/blog/introducing-personal-agent-protocol" rel="noopener noreferrer"&gt;Sierra's account&lt;/a&gt;, most personal agents already use company websites and apps the way people do, loading pages and clicking through forms, and when that can't finish the job they may call the support line or open web chat. The protocol's overview calls this "all or nothing": either the agent "passes as the user, signing in with their password and clicking through pages built for people," or the company blocks it, and "the company can't tell an agent from a person or offer it a better path." Neither source publishes a measurement. Where it matches your traffic, you can't tell an agent from your customer, and your customer can't limit what the agent does once it is in. On a platform that moves money, holds health records, or changes bookings, that is a question for your fraud and privacy owners as much as for your API team.&lt;/p&gt;

&lt;p&gt;Poppy's draft names the pieces that close that gap: agent identity by &lt;code&gt;client_id&lt;/code&gt;, signed-out Sessions under User IDs that carry no personal data, short-lived key-bound Session Tokens, revocation, and human handoff. That work is likely to carry over whichever customer-agent protocol wins, since PACT, the A2A-based parallel design, also asks for scopes, consent, and discovery. Draft 0.1 defines no audit log, so you design that part.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Poppy is a consent and session layer between a customer's personal agent and a company. It reuses the company's APIs, MCP server, website, or company agent as channels.&lt;/li&gt;
&lt;li&gt;As of October 12, 2026, the spec is draft 0.1 with breaking changes allowed, no official reference implementation is public, and we found no public evidence of production adoption.&lt;/li&gt;
&lt;li&gt;An OAuth API plus an MCP server already provides scoped delegated authorization. Draft 0.1 adds signed-out Sessions, per-User agent attribution with allow and block lists, key-bound tokens, human handoff, and revocation back to signed out.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What a customer's agent needs from your platform
&lt;/h2&gt;

&lt;p&gt;The spec's term for a customer's agent is personal agent. It is a new kind of client, and it needs six things: discovery (where your channels are and how to sign in), authorization (what the customer allowed), scoped Sessions tied to one agent and one customer, a route to a human, privacy for a customer who has not signed in, and somewhere to act. The status quo covers only the last. A browser agent holding the customer's password looks like the customer to you. &lt;a href="https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization" rel="noopener noreferrer"&gt;MCP's authorization spec&lt;/a&gt; gives you scoped OAuth for tools, but it defines no signed-out session, no per-company User ID with no personal data, no human handoff, and no session shared across channels. Poppy puts all six behind one documented interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  How a Poppy request flows, end to end
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://personalagentprotocol.org/docs/spec" rel="noopener noreferrer"&gt;draft spec&lt;/a&gt; walks one agent from discovery to sign-out, in this order (draft 0.1, so any step can change):&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%2Fu9slpwm3nau783asxcq1.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%2Fu9slpwm3nau783asxcq1.png" alt="Diagram: a Poppy session in six steps from personal agent to company. Start with poppy.json, signed out; sign in with poppy:read or poppy:write; key-bound token that lasts hours; act through an API, MCP server, website, or company agent; hand off to a person; revoke, which returns to signed out." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The draft 0.1 Session lifecycle, condensed.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Discover. "A Company MUST publish its &lt;code&gt;poppy.json&lt;/code&gt; document at &lt;code&gt;https://{domain}/.well-known/poppy.json&lt;/code&gt; over HTTPS." It lists your sign-in options (&lt;code&gt;auth&lt;/code&gt;) and channels (&lt;code&gt;agent&lt;/code&gt;, &lt;code&gt;apis&lt;/code&gt;, &lt;code&gt;web&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Identify the agent. "A Personal Agent's &lt;code&gt;client_id&lt;/code&gt; MUST be an HTTPS URL that returns its metadata document." You may require registration ahead of time and keep allowlists or blocklists by &lt;code&gt;client_id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Start signed out. The agent sends a signed assertion (a JWT bearer grant) to your &lt;code&gt;token_endpoint&lt;/code&gt;, naming itself (&lt;code&gt;iss&lt;/code&gt;) and the User (&lt;code&gt;sub&lt;/code&gt;). "A Session starts signed out": you can recognize the same User over time, but not who the User is. The User ID "MUST NOT contain or be derived from personal information, such as an email address, phone number, or name, even with a keyed hash."&lt;/li&gt;
&lt;li&gt;Sign in and grant scope. Sign-in is Direct ("the OAuth 2.0 authorization code flow with PKCE"), Device ("the OAuth 2.0 device authorization grant"), or Mediated, which "is not an OAuth flow." Base scopes are &lt;code&gt;poppy:read&lt;/code&gt; and &lt;code&gt;poppy:write&lt;/code&gt;, plus any you define. Sign-in returns an Account Token, "A long-lived credential from sign-in."&lt;/li&gt;
&lt;li&gt;Get a key-bound Session Token. The Account Token yields signed-in Session Tokens, "bound to a key the Personal Agent holds, so a copied token is useless without it," which "SHOULD last hours, not days."&lt;/li&gt;
&lt;li&gt;Act. The agent calls your APIs ("DPoP for OpenAPI, and Bearer for MCP"), gets a website cookie through &lt;code&gt;web.browser_session_endpoint&lt;/code&gt;, or talks to your company agent through the conversation API.&lt;/li&gt;
&lt;li&gt;Hand off. The agent sends "an empty JSON object" to &lt;code&gt;POST {endpoint}/{id}/handoff&lt;/code&gt;; the conversation's &lt;code&gt;responder&lt;/code&gt; is &lt;code&gt;agent&lt;/code&gt; or &lt;code&gt;human&lt;/code&gt;, and its status is &lt;code&gt;working&lt;/code&gt;, &lt;code&gt;idle&lt;/code&gt;, &lt;code&gt;queued&lt;/code&gt;, or &lt;code&gt;closed&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Close. An empty JSON object to &lt;code&gt;POST {endpoint}/{id}/close&lt;/code&gt; ends the conversation.&lt;/li&gt;
&lt;li&gt;Revoke or fail. The agent revokes its Account Token at your &lt;code&gt;revocation_endpoint&lt;/code&gt;, and you sign out every Session signed in with it: "Usually those Sessions continue signed out, so a cart or an open conversation carries on." Failures return &lt;code&gt;invalid_token&lt;/code&gt; (401), &lt;code&gt;sign_in_required&lt;/code&gt; (403), &lt;code&gt;insufficient_scope&lt;/code&gt; (403), &lt;code&gt;invalid_session&lt;/code&gt; (400), or &lt;code&gt;account_mismatch&lt;/code&gt; (400).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Poppy vs MCP vs A2A
&lt;/h2&gt;

&lt;p&gt;The last column is today's browser agent. Versions: Poppy draft 0.1, &lt;a href="https://modelcontextprotocol.io/specification/latest" rel="noopener noreferrer"&gt;MCP schema 2026-07-28&lt;/a&gt;, and &lt;a href="https://a2a-protocol.org/latest/specification/" rel="noopener noreferrer"&gt;A2A 1.0.0&lt;/a&gt;, as of October 12, 2026.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Poppy&lt;/th&gt;
&lt;th&gt;MCP&lt;/th&gt;
&lt;th&gt;A2A&lt;/th&gt;
&lt;th&gt;Status quo: agent passes as the user&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Job it owns&lt;/td&gt;
&lt;td&gt;Consent and sessions between a personal agent and a company&lt;/td&gt;
&lt;td&gt;Connecting LLM applications to external tools and data&lt;/td&gt;
&lt;td&gt;Communication between independent, "potentially opaque" agent systems&lt;/td&gt;
&lt;td&gt;Finishing a task through pages built for people&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where state lives&lt;/td&gt;
&lt;td&gt;A company-side Session per agent and User, signed out or in, shared by API, website, and conversation&lt;/td&gt;
&lt;td&gt;Stateless, self-contained JSON-RPC 2.0 requests between host, client, and server; the authorization spec defines no signed-out or cross-channel session&lt;/td&gt;
&lt;td&gt;In tasks, with states such as &lt;code&gt;TASK_STATE_COMPLETED&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The customer's web session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who acts for whom&lt;/td&gt;
&lt;td&gt;A personal agent for one User, toward one Company&lt;/td&gt;
&lt;td&gt;A host's client calling a server, optionally for an OAuth-authorized user&lt;/td&gt;
&lt;td&gt;One agent delegating to another&lt;/td&gt;
&lt;td&gt;The agent, indistinguishable from the customer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Execution context&lt;/td&gt;
&lt;td&gt;Your OpenAPI endpoints, MCP server, website, or company agent&lt;/td&gt;
&lt;td&gt;The MCP server's tools&lt;/td&gt;
&lt;td&gt;Inside the remote agent&lt;/td&gt;
&lt;td&gt;Your website, in a browser&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authorization and control boundary&lt;/td&gt;
&lt;td&gt;Direct, Device, or Mediated sign-in; base and custom scopes; key-bound tokens; &lt;code&gt;client_id&lt;/code&gt; allow and block lists&lt;/td&gt;
&lt;td&gt;Optional; when used, an OAuth 2.1 resource server with RFC 9728 metadata and scope step-up&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://a2a-protocol.org/latest/topics/agent-discovery/" rel="noopener noreferrer"&gt;Agent Card&lt;/a&gt; at &lt;code&gt;/.well-known/agent-card.json&lt;/code&gt;; a &lt;code&gt;TASK_STATE_AUTH_REQUIRED&lt;/code&gt; state&lt;/td&gt;
&lt;td&gt;The customer's password&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interoperability&lt;/td&gt;
&lt;td&gt;Lists OpenAPI and MCP servers as channels; the spec does not mention A2A&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://www.linuxfoundation.org/press/linux-foundation-announces-the-formation-of-the-agentic-ai-foundation" rel="noopener noreferrer"&gt;Founding project of the Agentic AI Foundation&lt;/a&gt; (December 9, 2025)&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://aaif.io/blog/a2a-joins-aaif" rel="noopener noreferrer"&gt;Agentic AI Foundation project since August 17, 2026&lt;/a&gt;; PACT builds on it&lt;/td&gt;
&lt;td&gt;None defined&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fallback path&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;handoff&lt;/code&gt; to a person; revocation drops Sessions to signed out&lt;/td&gt;
&lt;td&gt;None in the authorization spec&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;TASK_STATE_INPUT_REQUIRED&lt;/code&gt; marks a task that needs input&lt;/td&gt;
&lt;td&gt;The support line or web chat&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The layers stack: Poppy decides which agent is asking and what the customer allowed; an MCP server is one channel it can route that agent through.&lt;/p&gt;

&lt;p&gt;A2A's relationship to Poppy is unsettled. Neither the Poppy spec nor Sierra's posts mention A2A, so no primary source defines it; the independent tracker poppy.md &lt;a href="https://poppy.md/compare" rel="noopener noreferrer"&gt;calls the two "Mostly complementary, with one overlap to watch: the 'company agent' route."&lt;/a&gt; The A2A-based design is Decagon's &lt;a href="https://decagon.ai/blog/introducing-the-personal-agent-consent-trust-protocol-pact" rel="noopener noreferrer"&gt;PACT (Personal Agent Consent &amp;amp; Trust Protocol)&lt;/a&gt;, released October 6, 2026, the same day Sierra announced the Personal Agent Protocol, and "Built on the Agent2Agent protocol and OAuth 2.0." Decagon says PACT joins "the Personal Agent Protocol working group announced by the team behind Meta's Muse," though no Poppy spec text mentions PACT. For A2A's own production gaps, read &lt;a href="https://ekline.io/blog/a2a-production-readiness" rel="noopener noreferrer"&gt;A2A production readiness: what it takes beyond protocol support&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Poppy stands on October 12, 2026
&lt;/h2&gt;

&lt;p&gt;Maturity, implementations, and adoption are separate claims.&lt;/p&gt;

&lt;p&gt;Draft maturity. The &lt;a href="https://personalagentprotocol.org/docs/spec" rel="noopener noreferrer"&gt;spec's status line marks it Draft 0.1, updated October 9, 2026&lt;/a&gt;, under the Apache License 2.0, and warns that "Any part of it can change before a stable version, including in ways that aren't backward compatible." No editors, governance body, or standards organization appear on the spec, overview, or home page.&lt;/p&gt;

&lt;p&gt;Implementations. On October 9, &lt;a href="https://sierra.ai/blog/poppy" rel="noopener noreferrer"&gt;Sierra wrote&lt;/a&gt;, "Over the next month we will host design workshops and publish a reference implementation to help developers get started." As of October 12, the &lt;a href="https://poppy.md/implementations" rel="noopener noreferrer"&gt;independent tracker poppy.md&lt;/a&gt; says of the official reference implementation, "none is public yet." It lists only community and partner-demo work, such as a Cloudflare Workers demo, a demo of Meta's Muse shown only in an X video, and the independent &lt;a href="https://github.com/datalayer/personal-agent-protocol" rel="noopener noreferrer"&gt;Datalayer SDK&lt;/a&gt;, which implements "draft 0.1 (2026-10-09)."&lt;/p&gt;

&lt;p&gt;Production adoption. We found no public evidence of it. On October 9 the &lt;a href="https://github.com/orka-agents/orka/issues/774" rel="noopener noreferrer"&gt;orka project&lt;/a&gt; reported, "No company publishes a PAP file," after checking about 40 sites, including the launch partners: "Some sites blocked the check, and none of the rest had one." Sierra's October 9 post adds 35 design partners, including OpenAI, Visa, Mastercard, PayPal, Okta, and Bank of America, to the launch partners Meta, Genesys, Instinct, NiCE, Rocket, Shopify, Stripe, and Walmart. Design-partner status is not an implementation, and poppy.md lists no design-partner implementation beyond demos as of October 12. A &lt;a href="https://github.com/fillthemat/fillthemat/issues/123" rel="noopener noreferrer"&gt;go/no-go review in the fillthemat project's GitHub issues&lt;/a&gt; said of OpenAI's listing as a design partner, "This does not establish ChatGPT support or merchant traffic."&lt;/p&gt;

&lt;h2&gt;
  
  
  What your API team would need to build
&lt;/h2&gt;

&lt;p&gt;The minimum is small: a company implements "one of agent, apis, or web," and sign-in is optional: "With none, it offers only signed-out Sessions."&lt;/p&gt;

&lt;p&gt;An OAuth API plus an MCP server already covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scoped delegated authorization: under MCP's authorization spec, a protected server "acts as an OAuth 2.1 resource server," publishes RFC 9728 metadata that tells clients how to authorize, and can step up scope on &lt;code&gt;insufficient_scope&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Direct and Device sign-in, if your OAuth server runs PKCE and the device grant. The &lt;a href="https://github.com/OpenAgentsInc/openagents/issues/11239" rel="noopener noreferrer"&gt;OpenAgents team&lt;/a&gt; found this overlap: "We already have RFC 8414/9728 metadata, PKCE, a device flow (&lt;code&gt;coder login&lt;/code&gt;) and an MCP resource server."&lt;/li&gt;
&lt;li&gt;An API channel: an MCP server listed in &lt;code&gt;apis&lt;/code&gt; is a valid Poppy channel with Bearer tokens.&lt;/li&gt;
&lt;li&gt;Agent identity by URL: MCP's authorization spec says authorization servers and MCP clients "SHOULD support OAuth Client ID Metadata Documents," where the client uses an HTTPS URL as its &lt;code&gt;client_id&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Draft 0.1 adds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;poppy.json&lt;/code&gt; discovery file pointing at your existing endpoints.&lt;/li&gt;
&lt;li&gt;Agent and User attribution on every token, plus allow and block lists by &lt;code&gt;client_id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Signed-out Sessions started from the agent's signed assertion, with User IDs never derived from personal data.&lt;/li&gt;
&lt;li&gt;Key-bound Session Tokens that last hours, checked on OpenAPI calls with DPoP (RFC 9449), which proves the caller holds the agent's key.&lt;/li&gt;
&lt;li&gt;One Session across channels, including a &lt;code&gt;browser_session_endpoint&lt;/code&gt; whose cookie follows sign-in and sign-out.&lt;/li&gt;
&lt;li&gt;Handoff and close endpoints, if you offer a company agent, wired to a staffed queue.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;revocation_endpoint&lt;/code&gt; that signs out every Session tied to an Account Token.&lt;/li&gt;
&lt;li&gt;Audit records you design. The spec gives attribution ("the Company always knows a request comes from a Personal Agent, which one, and for which User") and defines no audit log.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For delegated OAuth in more depth, read &lt;a href="https://ekline.io/blog/oauth-for-ai-agents" rel="noopener noreferrer"&gt;OAuth for AI agents: why general-purpose agents strain the integration model&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The case against adding Poppy now, and where it holds
&lt;/h2&gt;

&lt;p&gt;The strongest objection is that an OAuth API, and maybe an MCP server, is enough, and a new draft protocol is premature. The evidence supports much of it. The spec can change incompatibly, and fillthemat found that "Datalayer shipped 11 non-stage npm versions Oct 10–11, including breaking changes." We found no adoption to serve, and PACT is a parallel design, though poppy.md notes it is "now an input to the Poppy working group rather than a rival, at least on paper." Three of the four public GitHub issues we found from October 9 to 12 stopped short of production: orka wrote, "We should not build anything yet," OpenAgents closed its issue as "Not on the V1 launch path," and fillthemat's review said "NO-GO for production rollout or substantial investment yet." The fourth is fillthemat's own &lt;a href="https://github.com/fillthemat/fillthemat/issues/173" rel="noopener noreferrer"&gt;follow-up spec&lt;/a&gt;: "The owner chose (2026-10-11) to build full support anyway, as a deliberate bet." That is a planned build, not adoption.&lt;/p&gt;

&lt;p&gt;On &lt;a href="https://news.ycombinator.com/item?id=49981921" rel="noopener noreferrer"&gt;Hacker News&lt;/a&gt;, in a thread with two comments, one commenter asked, "As a human directing my personal agent, why does it need a protocol?"&lt;/p&gt;

&lt;p&gt;On scoped authorization, the objection is right, and a large investment in a draft would be a poor trade. It falls short on the rest. MCP's authorization spec defines no signed-out session, per-company User ID, human handoff, or session shared across channels, and those gaps remain whatever protocol your customers' agents speak. PACT on A2A also asks for scopes, consent, and discovery of its own, so identity and session work is likely to carry over, whichever design wins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adopt, experiment, or wait
&lt;/h2&gt;

&lt;p&gt;For any draft standard, production work waits for stability and a counterpart; cheap, reversible work can start now.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adopt now when all of these hold
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The spec is stable.&lt;/li&gt;
&lt;li&gt;An official reference implementation is public.&lt;/li&gt;
&lt;li&gt;A personal agent your customers use sends Poppy traffic to services like yours.&lt;/li&gt;
&lt;li&gt;Audit records, fraud review, and a staffed queue are ready for &lt;code&gt;poppy:write&lt;/code&gt; actions and handoffs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As of October 12, 2026, the spec is not stable, no official reference implementation is public, and we found no public evidence of the third.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run a bounded experiment when these hold
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You already run OAuth with PKCE or a device flow, an MCP server, or a public OpenAPI.&lt;/li&gt;
&lt;li&gt;You see customer agents in your web or support traffic.&lt;/li&gt;
&lt;li&gt;You can cap the work and throw it away, as fillthemat's review capped a proposed spike at one engineer-day or less.&lt;/li&gt;
&lt;li&gt;The scope stays at &lt;code&gt;poppy.json&lt;/code&gt; over existing endpoints, signed-out read access, and no new write paths.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Wait when any of these hold
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You have no OAuth-scoped public API yet; build that first.&lt;/li&gt;
&lt;li&gt;No customer agents appear in your web or support traffic.&lt;/li&gt;
&lt;li&gt;Your write actions move money or change accounts, and you have no audit trail for agent actions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Waiting still leaves protocol-neutral work open: per-agent attribution, short token lifetimes, revocation, and an escalation route to a person.&lt;/p&gt;

&lt;h2&gt;
  
  
  Risks and open questions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Protocol churn: draft 0.1 allows breaking changes, so record the &lt;code&gt;protocol_version&lt;/code&gt; you implement and budget for rework.&lt;/li&gt;
&lt;li&gt;Identity and authorization: Mediated sign-in uses "credentials the User has given it for this Company." The agent still holds the customer's credentials, though you can now see it is an agent; decide whether to offer it.&lt;/li&gt;
&lt;li&gt;Consent: &lt;code&gt;poppy:write&lt;/code&gt; covers "Make changes, such as exchanging an item or updating a booking," so your custom scopes carry the consent design.&lt;/li&gt;
&lt;li&gt;Privacy: check that analytics and fraud tooling never join a signed-out User ID to an identity before sign-in.&lt;/li&gt;
&lt;li&gt;Auditability: the spec defines no audit log, so record agent, User, scope, Session, and action yourself.&lt;/li&gt;
&lt;li&gt;Abuse and fraud: registration, allow and block lists, &lt;code&gt;client_id&lt;/code&gt; revocation, and per-&lt;code&gt;client_id&lt;/code&gt; rate limits are among the spec's optional controls, so decide what fraud review applies to agent writes.&lt;/li&gt;
&lt;li&gt;Observability and versioning: tag logs and metrics by &lt;code&gt;client_id&lt;/code&gt;, and pin community SDK versions in any spike.&lt;/li&gt;
&lt;li&gt;Operational ownership: name owners for the token endpoints, the handoff queue, and &lt;code&gt;poppy.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Fallback to people: customers whose agent fails still need your existing support workflow.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Evaluation checklist
&lt;/h2&gt;

&lt;p&gt;Mark each yes or no for your own API:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your OAuth server supports the authorization code flow with PKCE and the device authorization grant.&lt;/li&gt;
&lt;li&gt;Your MCP server, if any, acts as an OAuth 2.1 resource server with RFC 9728 metadata.&lt;/li&gt;
&lt;li&gt;Your API can validate DPoP-bound tokens, or you have sized that work.&lt;/li&gt;
&lt;li&gt;Your token endpoint can issue a signed-out Session for an agent and a User ID with no personal data.&lt;/li&gt;
&lt;li&gt;You can identify agents by HTTPS &lt;code&gt;client_id&lt;/code&gt; and keep allow and block lists.&lt;/li&gt;
&lt;li&gt;One revocation signs out every Session tied to an account while carts and conversations continue.&lt;/li&gt;
&lt;li&gt;A staffed human queue sits behind any agent handoff.&lt;/li&gt;
&lt;li&gt;Your logs record agent, User, scope, and action for every request.&lt;/li&gt;
&lt;li&gt;You see customer agents in your traffic today.&lt;/li&gt;
&lt;li&gt;One named person owns the experiment and tracks the spec.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Yes on 1 or 2, plus 9 and 10, points to an experiment. No on both 1 and 2 points to waiting. Yes across the list, plus a stable spec and an agent counterpart, points to adoption.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is Poppy the same as the Personal Agent Protocol?
&lt;/h3&gt;

&lt;p&gt;Yes. The overview says "Personal Agent Protocol (Poppy for short)." Sierra's October 6, 2026 announcement used only the full name; its October 9 post added "known as Poppy."&lt;/p&gt;

&lt;h3&gt;
  
  
  Who maintains it?
&lt;/h3&gt;

&lt;p&gt;Sierra and Meta develop it with industry partners under the Apache License 2.0. As of October 12, 2026, no editors, governance body, or standards organization appear on the spec site.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is the spec final?
&lt;/h3&gt;

&lt;p&gt;No. It is draft 0.1, updated October 9, 2026, and any part can change before a stable version, including in ways that are not backward compatible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Poppy replace MCP or A2A?
&lt;/h3&gt;

&lt;p&gt;No. Poppy lists MCP servers and OpenAPI endpoints as channels, so it sits on top of an MCP server you already run. The spec does not mention A2A, and no primary source defines that relationship.&lt;/p&gt;

&lt;h3&gt;
  
  
  How is it different from PACT?
&lt;/h3&gt;

&lt;p&gt;Decagon's PACT is "Built on the Agent2Agent protocol and OAuth 2.0," and its agents find a business through an A2A Agent Card. Poppy uses &lt;code&gt;poppy.json&lt;/code&gt; and routes agents to OpenAPI, MCP, the website, or a company agent. Decagon's PACT is a different document from the &lt;a href="https://datatracker.ietf.org/doc/draft-valverde-oauth-pact/" rel="noopener noreferrer"&gt;IETF individual draft also named PACT&lt;/a&gt; (Private Agent Consent and Trust Profile, April 2026); neither references the other.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is there a reference implementation?
&lt;/h3&gt;

&lt;p&gt;Not an official one as of October 12, 2026. Sierra said on October 9 it would publish one "Over the next month." Independent community SDKs and demos exist.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should our API support Poppy now?
&lt;/h3&gt;

&lt;p&gt;For most consumer-facing platforms, as of October 12, 2026, run a bounded experiment with signed-out read access and no new write paths, and hold production rollout until the adopt conditions are met.&lt;/p&gt;

&lt;p&gt;Run the evaluation checklist against your own API with the &lt;a href="https://personalagentprotocol.org/docs/spec" rel="noopener noreferrer"&gt;draft 0.1 specification&lt;/a&gt; open beside it, and record your adopt, experiment, or wait decision with its reasons.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>api</category>
      <category>mcp</category>
    </item>
    <item>
      <title>How do you evaluate AI support quality beyond deflection rate?</title>
      <dc:creator>Alisa Hester</dc:creator>
      <pubDate>Tue, 13 Oct 2026 00:30:05 +0000</pubDate>
      <link>https://dev.to/alisa_hester_1/how-do-you-evaluate-ai-support-quality-beyond-deflection-rate-4b37</link>
      <guid>https://dev.to/alisa_hester_1/how-do-you-evaluate-ai-support-quality-beyond-deflection-rate-4b37</guid>
      <description>&lt;p&gt;Leadership asks whether the AI is working, and the number you can defend is deflection or containment. What you cannot say is whether the customers behind it got a correct answer, finished the task, trusted the experience, or came back with the same problem. A deflected conversation can hide a customer who retried, reopened a ticket, escalated by email, churned, or acted on an incorrect answer.&lt;/p&gt;

&lt;p&gt;Deflection records where the work went. Whether the customer got a correct answer is a separate question, and your vendor's "resolution" figure does not settle it: each vendor sets its own window, judge, and stance on accuracy, and for some it is the unit you pay for.&lt;/p&gt;

&lt;p&gt;Our recommendation: keep deflection as an efficiency line and build your own scorecard around it. Rank answer correctness and verified resolution above efficiency, check each resolution against repeat contact in a fixed window per issue type, and segment by issue risk. Then run one bounded 30-day experiment with a decision rule you write before the data comes in.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Deflection and containment are cheap, real-time efficiency metrics and weak quality metrics.&lt;/li&gt;
&lt;li&gt;Vendor "resolution" numbers differ in window, judge, and accuracy criterion, and Intercom and Zendesk bill on them.&lt;/li&gt;
&lt;li&gt;Grade a small, stratified sample with reviewers who do not configure the bot; correctness and safety fails override other scores.&lt;/li&gt;
&lt;li&gt;Joining conversations to later outcomes shows association; only a holdout or staggered rollout shows cause.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What deflection can and cannot tell you
&lt;/h2&gt;

&lt;p&gt;Salesforce's Agentforce module defines Deflection Rate as the &lt;a href="https://trailhead.salesforce.com/content/learn/modules/agent-analytics-and-monitoring/monitor-agent-metrics-and-scores" rel="noopener noreferrer"&gt;"Share of sessions that ended without escalation to a support representative or another agent."&lt;/a&gt; Decagon, which sells an AI support agent, calls deflection &lt;a href="https://decagon.ai/glossary/what-is-the-difference-between-resolution-rate-and-deflection-rate" rel="noopener noreferrer"&gt;"a routing statistic" and resolution "an outcome statistic,"&lt;/a&gt; and says "they can move in opposite directions from the same change to an AI agent." The mechanism holds without the vendor: hiding the handoff raises deflection whether or not more customers get answers.&lt;/p&gt;

&lt;h3&gt;
  
  
  The denominator decides the number
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Deflection over all AI-touched sessions = sessions with no handoff ÷ all sessions where the AI replied.&lt;/li&gt;
&lt;li&gt;Deflection over real questions = sessions with no handoff ÷ (AI sessions minus greetings, test traffic, and sessions abandoned at the first message).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first counts a customer who typed "hi" and left as a success. The second still counts everyone who left after a wrong answer. Vendors choose too: &lt;a href="https://www.intercom.com/help/en/articles/8205718-fin-ai-agent-resolutions" rel="noopener noreferrer"&gt;Intercom's Fin resolution count&lt;/a&gt; leaves out greetings, unanswered clarifying questions, and explicit requests for a human. Write your denominator down and keep it fixed.&lt;/p&gt;

&lt;h3&gt;
  
  
  A session without a handoff has three explanations
&lt;/h3&gt;

&lt;p&gt;The customer was solved, gave up, or switched channels, and session data cannot tell which. SQM Group's survey method for phone first-call resolution counts a call only with "yes" to "Was your call resolved?", one call, and "no" to using another channel (&lt;a href="https://sqmgroup.com/resources/library/blog/calculate-first-call-resolution-rate" rel="noopener noreferrer"&gt;SQM, Jan 19, 2021, phone contact centers&lt;/a&gt;). Your AI data needs the same check: join sessions to later contacts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vendor "resolution" is several different metrics
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Definition&lt;/th&gt;
&lt;th&gt;What counts&lt;/th&gt;
&lt;th&gt;Window&lt;/th&gt;
&lt;th&gt;Who judges&lt;/th&gt;
&lt;th&gt;Accuracy a criterion?&lt;/th&gt;
&lt;th&gt;Billing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Intercom Fin resolution&lt;/td&gt;
&lt;td&gt;Customer confirms, or "exits the conversation without requesting further assistance (assumed resolution)"&lt;/td&gt;
&lt;td&gt;None stated; Fin can follow up after 4 minutes of inactivity&lt;/td&gt;
&lt;td&gt;Customer behavior&lt;/td&gt;
&lt;td&gt;Not stated&lt;/td&gt;
&lt;td&gt;$0.99 per resolution, "one outcome per conversation"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zendesk Verified Resolution&lt;/td&gt;
&lt;td&gt;An LLM reads the conversation "to confirm that the customer's request was satisfactorily resolved"&lt;/td&gt;
&lt;td&gt;Messaging 2 hours, up to 72; email 72 hours; voice on hangup&lt;/td&gt;
&lt;td&gt;The vendor's LLM&lt;/td&gt;
&lt;td&gt;Not named&lt;/td&gt;
&lt;td&gt;Draws from the resolution allowance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ada automated resolution&lt;/td&gt;
&lt;td&gt;"Relevant," "Accurate," and "Safe," on an LLM-graded sample of closed conversations&lt;/td&gt;
&lt;td&gt;Not stated&lt;/td&gt;
&lt;td&gt;The vendor's LLM&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Not stated&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None of these can be compared across tools, and two are billing units. Sources, for the definitions only: &lt;a href="https://www.intercom.com/help/en/articles/8205718-fin-ai-agent-resolutions" rel="noopener noreferrer"&gt;Intercom Help&lt;/a&gt;, &lt;a href="https://support.zendesk.com/hc/en-us/articles/9570369117338-About-automated-resolution-tiers" rel="noopener noreferrer"&gt;Zendesk's tiers, "introduced on May 18, 2026"&lt;/a&gt;, and &lt;a href="https://www.ada.cx/resources/guide/what-is-automated-resolution-in-ai-customer-service/" rel="noopener noreferrer"&gt;Ada's guide (June 17, 2026)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Drag, an inbox vendor that competes with these tools, gives an &lt;a href="https://www.dragapp.com/blog/ai-support-agent-resolution-rates/" rel="noopener noreferrer"&gt;example (Aug 8, 2026)&lt;/a&gt; without saying whether the numbers come from a real queue: "A single support queue, measured all four ways, produces four numbers that can span thirty points."&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Confirmed resolution: 46%&lt;/li&gt;
&lt;li&gt;Assumed resolution: 68%&lt;/li&gt;
&lt;li&gt;Deflection: 80%&lt;/li&gt;
&lt;li&gt;Satisfaction: 74%&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CSAT does not close the gap. &lt;a href="https://www.intercom.com/help/en/articles/10495092-understand-customer-experience-at-scale-with-the-customer-experience-cx-score" rel="noopener noreferrer"&gt;Intercom's CX Score&lt;/a&gt; scores closed chat or email conversations that include "at least two customer replies and two agent/chatbot replies," and Intercom says shorter conversations may now be rated when there is "a clear signal to evaluate." By our reading, a customer who leaves Fin after one exchange can count as an assumed resolution and still get no quality score, which is where silent failures are likely to sit.&lt;/p&gt;

&lt;h3&gt;
  
  
  The objection: deflection and CSAT are good enough
&lt;/h3&gt;

&lt;p&gt;Deflection is cheap and real-time, sampling is slow and noisy, and vendors already report resolution, so keep deflection as the efficiency line. Vendor resolution follows rules each vendor sets. Zendesk's billable Verified Resolution is graded by Zendesk's own LLM, and Intercom also bills for assumed resolutions, which it infers from customer silence. CSAT and conversation scores can miss the short exchanges where silent failures sit. The only QA price we found is for Lorikeet's automated QA product, Coach: &lt;a href="https://www.lorikeetcx.ai/articles/resolution-rate-vs-deflection-rate-ai-support" rel="noopener noreferrer"&gt;"about $0.27–$0.30 per ticket."&lt;/a&gt; We found no measured cost for human sampling. NIST's voluntary Generative AI Profile, which abbreviates generative AI as GAI, advises: &lt;a href="https://nvlpubs.nist.gov/nistpubs/ai/NIST.AI.600-1.pdf" rel="noopener noreferrer"&gt;"Avoid extrapolating GAI system performance or capabilities from narrow, non-systematic, and anecdotal assessments"&lt;/a&gt; (MS-2.5-001), which favors a small systematic sample over ad hoc spot checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  A measurement hierarchy
&lt;/h2&gt;

&lt;p&gt;Rank metrics so a gain lower down never excuses a loss higher up:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Safety and answer correctness.&lt;/li&gt;
&lt;li&gt;Task or issue resolution, verified against repeat contact.&lt;/li&gt;
&lt;li&gt;Escalation quality and context preservation.&lt;/li&gt;
&lt;li&gt;Customer effort, confidence, and trust.&lt;/li&gt;
&lt;li&gt;Operational efficiency and cost.&lt;/li&gt;
&lt;li&gt;Product, customer, and business outcomes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Correctness comes first because a confident wrong answer looks like a success in every routing metric. NIST AI 600-1 (July 2024) defines confabulation as "The production of confidently stated but erroneous or false content ... by which users may be misled or deceived."&lt;/p&gt;

&lt;p&gt;The concrete case is &lt;a href="https://manatt.com/insights/newsletters/advertising-law/ai-gone-wild-airline-has-to-honor-a-refund-policy" rel="noopener noreferrer"&gt;Moffatt v. Air Canada (2024 BCCRT 149)&lt;/a&gt;. Air Canada argued it could not be held liable for what its chatbot said. The tribunal found the airline "did not take reasonable care to ensure its chatbot was accurate," a "negligent misrepresentation," and awarded "$650.88 CAD" plus interest and fees, according to Manatt's summary (Feb 28, 2024). It is a decision of British Columbia's Civil Resolution Tribunal, a small-claims body. A deflection count cannot separate that conversation from a correct one.&lt;/p&gt;

&lt;p&gt;Efficiency sits fifth because speed and quality can move apart. In measured research on &lt;a href="https://arxiv.org/abs/2304.11771" rel="noopener noreferrer"&gt;"5,172 customer support agents"&lt;/a&gt; (arXiv v2, Nov 6, 2024; an assistant for human agents, not a customer-facing bot), Brynjolfsson, Li and Raymond found AI assistance raised issues resolved per hour "by 15% on average," while the "most experienced and highest-skilled workers see small gains in speed and small declines in quality."&lt;/p&gt;

&lt;h2&gt;
  
  
  Metric definitions, formulas, and gaming risks
&lt;/h2&gt;

&lt;p&gt;The formulas are our recommendation; keep each definition fixed for any comparison.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Definition or formula&lt;/th&gt;
&lt;th&gt;What it indicates&lt;/th&gt;
&lt;th&gt;How to instrument&lt;/th&gt;
&lt;th&gt;Main failure mode or gaming risk&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Deflection&lt;/td&gt;
&lt;td&gt;Sessions with no handoff ÷ eligible AI sessions, denominator stated&lt;/td&gt;
&lt;td&gt;Where the work went&lt;/td&gt;
&lt;td&gt;Handoff event&lt;/td&gt;
&lt;td&gt;Rises when the handoff is hidden; counts give-ups as wins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Containment&lt;/td&gt;
&lt;td&gt;AI sessions completed with no request for more help ÷ eligible AI sessions&lt;/td&gt;
&lt;td&gt;The AI kept the conversation&lt;/td&gt;
&lt;td&gt;Session-end event and "no further request" flag&lt;/td&gt;
&lt;td&gt;Counts silent exits; the customer may return by email&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Assumed vs confirmed resolution&lt;/td&gt;
&lt;td&gt;Assumed: no further request after the last AI answer. Confirmed: the customer said it worked&lt;/td&gt;
&lt;td&gt;How much of "resolved" rests on silence&lt;/td&gt;
&lt;td&gt;Exit and confirmation events&lt;/td&gt;
&lt;td&gt;Assumed rises with abandonment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verified resolution&lt;/td&gt;
&lt;td&gt;Resolutions passing the correctness check with no repeat contact in the window ÷ eligible sessions&lt;/td&gt;
&lt;td&gt;A correct answer that held&lt;/td&gt;
&lt;td&gt;QA sample and repeat-contact join&lt;/td&gt;
&lt;td&gt;Vendor versions are graded by the vendor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First-contact resolution&lt;/td&gt;
&lt;td&gt;Issues resolved on the first contact in any channel ÷ issues&lt;/td&gt;
&lt;td&gt;One contact was enough&lt;/td&gt;
&lt;td&gt;Issue ID across channels; SQM uses "within 1 to 30 days" by call reason&lt;/td&gt;
&lt;td&gt;A short window hides failures&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repeat contact in window&lt;/td&gt;
&lt;td&gt;Customers contacting again on the same issue within its window ÷ resolved issues&lt;/td&gt;
&lt;td&gt;The answer did not hold&lt;/td&gt;
&lt;td&gt;Join on customer ID, issue category, timestamp&lt;/td&gt;
&lt;td&gt;Loose issue tags hide repeats; one window for all issues&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reopen&lt;/td&gt;
&lt;td&gt;Tickets reopened ÷ tickets closed after AI involvement&lt;/td&gt;
&lt;td&gt;Closure was premature&lt;/td&gt;
&lt;td&gt;Ticket status history&lt;/td&gt;
&lt;td&gt;Lorikeet warns a 24-hour window can "close before the customer has noticed the problem is still there"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Escalation rate&lt;/td&gt;
&lt;td&gt;AI sessions handed to a person ÷ eligible AI sessions&lt;/td&gt;
&lt;td&gt;How often the AI needs help&lt;/td&gt;
&lt;td&gt;Handoff event&lt;/td&gt;
&lt;td&gt;Lowered by hiding the handoff&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Escalation quality&lt;/td&gt;
&lt;td&gt;Handoffs where context arrived and the customer did not repeat themselves ÷ handoffs&lt;/td&gt;
&lt;td&gt;Context survived the transfer&lt;/td&gt;
&lt;td&gt;QA sample of handoffs&lt;/td&gt;
&lt;td&gt;Measuring transfer speed instead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Customer effort&lt;/td&gt;
&lt;td&gt;Reported effort, plus contacts and channels per issue&lt;/td&gt;
&lt;td&gt;How hard help was to get&lt;/td&gt;
&lt;td&gt;Survey after resolution; contacts per issue&lt;/td&gt;
&lt;td&gt;In-session surveys miss customers who left&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time to resolution&lt;/td&gt;
&lt;td&gt;First contact to verified resolution, across channels&lt;/td&gt;
&lt;td&gt;Speed of an answer that held&lt;/td&gt;
&lt;td&gt;Issue timestamps&lt;/td&gt;
&lt;td&gt;Measured to session end instead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Answer correctness and groundedness&lt;/td&gt;
&lt;td&gt;Sampled answers passing the gated check ÷ sampled answers&lt;/td&gt;
&lt;td&gt;Right and backed by your sources&lt;/td&gt;
&lt;td&gt;QA rubric and citation check; Salesforce's "Answer Faithfulness" is one vendor version&lt;/td&gt;
&lt;td&gt;Unreviewed vendor auto-scores; convenience samples&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Business outcomes&lt;/td&gt;
&lt;td&gt;Churn, feature activation, expansion, or refunds in a fixed window for AI-served accounts, against a baseline&lt;/td&gt;
&lt;td&gt;Downstream association&lt;/td&gt;
&lt;td&gt;Account ID joined to billing and product events&lt;/td&gt;
&lt;td&gt;Read as causation; groups differ before the AI touches them&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each efficiency metric can rise without helping a customer; pair it with a quality metric.&lt;/p&gt;

&lt;h2&gt;
  
  
  A QA rubric for sampled AI conversations
&lt;/h2&gt;

&lt;p&gt;Correctness and policy or safety are gating: a fail on either fails the conversation, whatever the other scores say.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Scale&lt;/th&gt;
&lt;th&gt;Fails when&lt;/th&gt;
&lt;th&gt;Gating&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Correctness and groundedness&lt;/td&gt;
&lt;td&gt;Pass or fail&lt;/td&gt;
&lt;td&gt;A claim contradicts the product, a policy, or the source, or rests on no source you can find&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Policy and safety&lt;/td&gt;
&lt;td&gt;Pass or fail&lt;/td&gt;
&lt;td&gt;Promises a refund, exception, or commitment policy does not allow; exposes account data&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Completeness&lt;/td&gt;
&lt;td&gt;0 to 2&lt;/td&gt;
&lt;td&gt;Answers part of the question or stops a step short&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Actionability&lt;/td&gt;
&lt;td&gt;0 to 2&lt;/td&gt;
&lt;td&gt;The customer cannot act without asking again&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cited documentation was current&lt;/td&gt;
&lt;td&gt;Current, stale, missing, or no citation&lt;/td&gt;
&lt;td&gt;Cites an article that no longer matches the product, or needed one that does not exist&lt;/td&gt;
&lt;td&gt;No; route to the docs owner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tone, only where relevant&lt;/td&gt;
&lt;td&gt;0 to 2&lt;/td&gt;
&lt;td&gt;In a billing dispute, outage, or complaint, ignores the stated problem&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  How to sample
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Stratify by risk segment and oversample high-risk ones. Include assumed resolutions and short conversations your quality score may not rate.&lt;/li&gt;
&lt;li&gt;Use reviewers who do not configure the bot. NIST MS-1.3-003: "Verify those conducting structured human feedback exercises are not directly involved in system development tasks for the same GAI model."&lt;/li&gt;
&lt;li&gt;Calibrate weekly: two reviewers grade a shared subset, and each disagreement becomes a rubric note.&lt;/li&gt;
&lt;li&gt;Open every cited article. NIST MS-2.5-003: "Review and verify sources and citations in GAI system outputs during pre-deployment risk measurement and ongoing monitoring activities."&lt;/li&gt;
&lt;li&gt;Size per segment: if you would act on a failure that occurs once in N conversations, reading about 3N from that segment gives roughly a 95% chance of seeing it at least once (our arithmetic: 1 minus (1 minus 1/N) to the power 3N is close to 95%). Twig, which sells AI support, suggests &lt;a href="https://www.twig.so/blog/what-metrics-track-after-launching-ai-customer-support" rel="noopener noreferrer"&gt;"typically 50-100 interactions per week"&lt;/a&gt; (vendor guidance).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When the failure is the article
&lt;/h2&gt;

&lt;p&gt;The rubric line "cited documentation was current" separates two fixes. When sampled failures trace to a missing or stale article, route them to the documentation owner: the fix is a new or corrected article. Gartner's measured figure, reported by &lt;a href="https://www.destinationcrm.com/Articles/CRM-Insights/Insight/Only-14-of-Customer-Service-Issues-Are-Fully-Resolved-in-Self-Service-165841.aspx" rel="noopener noreferrer"&gt;destinationCRM (Sep 12, 2024)&lt;/a&gt;, covers self-service in general, not AI accuracy: "in 43 percent of cases, customers couldn't find content relevant to their issues."&lt;/p&gt;

&lt;p&gt;EkLine works on that layer: Docs Agent &lt;a href="https://docs.ekline.io/agent/manage-knowledge-base/" rel="noopener noreferrer"&gt;drafts and edits articles in a Confluence or Pylon knowledge base&lt;/a&gt; from sources such as support threads. A person reviews the diff under View All Changes and clicks Update KB, which "overwrites the live page immediately," so nothing goes live without that approval. Two &lt;a href="https://docs.ekline.io/agent/scheduled-agents/" rel="noopener noreferrer"&gt;scheduled agents&lt;/a&gt; help: "Improve Pylon knowledge base" reviews recent Pylon tickets for questions the knowledge base should have answered and drafts articles for your review, and "Confluence Knowledge Base Drift Review" detects Confluence pages that recent documentation changes made stale and drafts corrections. Knowledge base management is available on request.&lt;/p&gt;

&lt;p&gt;Before, a stale-article cluster from your sample waits in a backlog; after, a drafted correction waits for a reviewer. EkLine drafts the article fix; your scorecard still decides whether anything improved. Start with the &lt;a href="https://docs.ekline.io/agent/tutorial-answer-support-questions/" rel="noopener noreferrer"&gt;tutorial on turning recurring support questions into a knowledge base article&lt;/a&gt;, whose last step includes "Watch whether the question returns."&lt;/p&gt;

&lt;h2&gt;
  
  
  Join conversations to what happened next
&lt;/h2&gt;

&lt;p&gt;Fix each signal's key and window before you look at results.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Example join key&lt;/th&gt;
&lt;th&gt;Window&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repeat contact or later escalation&lt;/td&gt;
&lt;td&gt;Customer or account ID plus issue category, any channel&lt;/td&gt;
&lt;td&gt;Per issue type&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reopen&lt;/td&gt;
&lt;td&gt;Ticket ID&lt;/td&gt;
&lt;td&gt;Until auto-close&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Task completion&lt;/td&gt;
&lt;td&gt;Account ID plus the product event the answer described&lt;/td&gt;
&lt;td&gt;Per issue type&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feature activation&lt;/td&gt;
&lt;td&gt;Account ID plus feature event&lt;/td&gt;
&lt;td&gt;Fixed in advance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Refund or churn risk&lt;/td&gt;
&lt;td&gt;Account ID plus billing events&lt;/td&gt;
&lt;td&gt;Fixed in advance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Support-assisted revenue&lt;/td&gt;
&lt;td&gt;Account ID plus expansion records&lt;/td&gt;
&lt;td&gt;Fixed in advance&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Set each window by how long a customer takes to discover a wrong answer. SQM sets phone windows by call reason: a claim "might need 30 days" and a status inquiry "might only need 5 days." Vendor guidance runs shorter: Decagon says "Recontact rate within 48 or 72 hours is the cheapest check on an inflated resolution figure," and &lt;a href="https://www.zendesk.com/blog/ai/workflow-automation/ticket-deflection-vs-resolution/" rel="noopener noreferrer"&gt;Zendesk's blog&lt;/a&gt; uses 24 to 72 hours. One practitioner, &lt;a href="https://tianpan.co/blog/2026-05-27-the-deflection-metric-that-lied" rel="noopener noreferrer"&gt;Tian Pan&lt;/a&gt;, recommends "Repeat-contact rate at 7, 30, and 90 days."&lt;/p&gt;

&lt;p&gt;These joins measure association. Customers who stay with the AI can differ from those who ask for a person first, so lower churn among them can reflect who they were. To show cause, use a holdout (eligible conversations in a segment randomly routed to the AI or the human path) or a staggered rollout (segments switched on in an order fixed in advance, compared with segments still waiting). Both need the same definitions on each side, a primary metric and window chosen before launch, and time for lagging outcomes to appear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Segment before you average
&lt;/h2&gt;

&lt;p&gt;Report every metric by issue type, customer tier, product area, risk (money, account access, security, and policy commitments in the high tier), and channel. Averages hide harmful failures in small high-risk segments: if password resets dominate your volume, the blended rate can rise while answers about a changed API endpoint or a refund policy get worse. NIST says organizations "may additionally wish to allocate risk management resources relative to the severity and likelihood of negative impacts" (AI 600-1, p. 3), and SQM's per-reason windows apply the same logic to time. No source we found measures how often this happens; it follows from how averages work.&lt;/p&gt;

&lt;h2&gt;
  
  
  An example balanced scorecard
&lt;/h2&gt;

&lt;p&gt;Illustrative layout, fields and formulas only. Values come from your own human and AI baselines. The only published averages we found are vendor figures on the vendor's own definition.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Line&lt;/th&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Formula&lt;/th&gt;
&lt;th&gt;Segment by&lt;/th&gt;
&lt;th&gt;Review cadence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Guardrail&lt;/td&gt;
&lt;td&gt;Gated correctness pass rate&lt;/td&gt;
&lt;td&gt;Sampled conversations passing ÷ sampled conversations&lt;/td&gt;
&lt;td&gt;Risk tier, issue type&lt;/td&gt;
&lt;td&gt;Weekly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Guardrail&lt;/td&gt;
&lt;td&gt;Policy and safety fails&lt;/td&gt;
&lt;td&gt;Count of gated policy fails in the sample&lt;/td&gt;
&lt;td&gt;Risk tier&lt;/td&gt;
&lt;td&gt;Weekly; high-risk fails reviewed that week&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leading&lt;/td&gt;
&lt;td&gt;Verified resolution rate&lt;/td&gt;
&lt;td&gt;As in the matrix&lt;/td&gt;
&lt;td&gt;Issue type, channel&lt;/td&gt;
&lt;td&gt;Weekly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leading&lt;/td&gt;
&lt;td&gt;Repeat contact in window&lt;/td&gt;
&lt;td&gt;Repeat contacts on the same issue ÷ resolved issues&lt;/td&gt;
&lt;td&gt;Issue type&lt;/td&gt;
&lt;td&gt;Weekly, as windows close&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leading&lt;/td&gt;
&lt;td&gt;Escalation quality&lt;/td&gt;
&lt;td&gt;Handoffs with context preserved ÷ handoffs&lt;/td&gt;
&lt;td&gt;Product area&lt;/td&gt;
&lt;td&gt;Weekly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leading&lt;/td&gt;
&lt;td&gt;Documentation currency&lt;/td&gt;
&lt;td&gt;Answers citing a current article ÷ answers with a citation&lt;/td&gt;
&lt;td&gt;Product area&lt;/td&gt;
&lt;td&gt;Weekly, to the knowledge base owner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Efficiency&lt;/td&gt;
&lt;td&gt;Deflection, denominator stated&lt;/td&gt;
&lt;td&gt;Sessions with no handoff ÷ eligible AI sessions&lt;/td&gt;
&lt;td&gt;Channel&lt;/td&gt;
&lt;td&gt;Real time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lagging&lt;/td&gt;
&lt;td&gt;Refund or churn in window&lt;/td&gt;
&lt;td&gt;Accounts with the event ÷ accounts in the group, AI beside human baseline&lt;/td&gt;
&lt;td&gt;Customer tier&lt;/td&gt;
&lt;td&gt;Monthly&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  A 30-day plan
&lt;/h2&gt;

&lt;p&gt;Owners: the support leader (decision rule), Support Ops (definitions and instrumentation), reviewers outside the bot team (sample), and the knowledge base owner (documentation failures).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Week&lt;/th&gt;
&lt;th&gt;Do&lt;/th&gt;
&lt;th&gt;Checkpoint&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Establish definitions, denominators, segments, and per-issue windows. Write the decision rule (expand, hold, or pull back) before any data comes in.&lt;/td&gt;
&lt;td&gt;Signed definitions; dated decision rule&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Instrument handoff, exit, confirmation, and reopen events and build the joins. Create the sample and audit process.&lt;/td&gt;
&lt;td&gt;Events firing; first calibration session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Baseline human and AI flows on the same definitions and windows. Grade the weekly sample.&lt;/td&gt;
&lt;td&gt;Side-by-side baseline per segment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Review failure clusters: wrong answers, stale articles, lost context. Apply the decision rule as written.&lt;/td&gt;
&lt;td&gt;Expand, hold, or pull back, per segment&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A rule might read: expand in a segment when its sample shows no gated fails and repeat contact is no worse than the human baseline; pull back on any high-risk safety fail or repeat contact worse than the baseline; hold otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Replace the deflection target with one experiment
&lt;/h2&gt;

&lt;p&gt;An illustrative bounded experiment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hypothesis: AI answers to refund-policy questions are correct and hold as well as human answers.&lt;/li&gt;
&lt;li&gt;Segment: refund-policy questions in chat, all tiers (high risk).&lt;/li&gt;
&lt;li&gt;Primary signal: repeat contact on the same issue within 30 days, a claim-type window.&lt;/li&gt;
&lt;li&gt;Guardrail: gated correctness and policy pass rate in a weekly stratified sample.&lt;/li&gt;
&lt;li&gt;Window: 30 days of conversations, plus 30 days for the last window to close.&lt;/li&gt;
&lt;li&gt;Decision rule: written in week 1 and applied as written.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bring that segment to your next leadership review, with deflection beside it as the efficiency line.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is a high deflection rate a good thing?
&lt;/h3&gt;

&lt;p&gt;It means the AI kept the conversation, which lowers cost if the answers were right. It cannot separate solved customers from those who gave up or switched channels, so read it beside verified resolution and repeat contact.&lt;/p&gt;

&lt;h3&gt;
  
  
  What resolution rate should an AI support agent achieve?
&lt;/h3&gt;

&lt;p&gt;We found no cross-vendor benchmark for resolution, repeat contact, or accuracy. Vendor averages, such as the one &lt;a href="https://www.ada.cx/resources/guide/what-is-automated-resolution-in-ai-customer-service/" rel="noopener noreferrer"&gt;Ada publishes&lt;/a&gt; for its own automated resolution rate, use that vendor's definition and do not compare across tools. Set targets from your own human baseline, segment by segment.&lt;/p&gt;

&lt;h3&gt;
  
  
  How can false deflection negatively impact customers?
&lt;/h3&gt;

&lt;p&gt;A customer counted as deflected may have acted on a wrong answer, retried elsewhere, or left. In Moffatt v. Air Canada, a ruling by British Columbia's Civil Resolution Tribunal, a small-claims body, a chatbot's inaccurate answer led to a finding of negligent misrepresentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  How should deflection and resolution metrics be reported to leadership?
&lt;/h3&gt;

&lt;p&gt;As separate lines on one scorecard: guardrails first, then verified resolution and repeat contact, then efficiency, each by segment. State every denominator and window, and label downstream outcomes as association.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>analytics</category>
      <category>support</category>
      <category>saas</category>
    </item>
    <item>
      <title>How to keep Fumadocs docs in sync with your code</title>
      <dc:creator>Alisa Hester</dc:creator>
      <pubDate>Mon, 12 Oct 2026 15:43:01 +0000</pubDate>
      <link>https://dev.to/alisa_hester_1/how-to-keep-fumadocs-docs-in-sync-with-your-code-239c</link>
      <guid>https://dev.to/alisa_hester_1/how-to-keep-fumadocs-docs-in-sync-with-your-code-239c</guid>
      <description>&lt;p&gt;To keep Fumadocs docs in sync with a Next.js app and its API, let Fumadocs generate every page it can from code (OpenAPI reference pages and TypeScript props tables), then give the hand-written guides around those pages an update loop of their own. A generated page changes when its source changes. A guide, a migration note, or a usage example changes only when someone edits it.&lt;/p&gt;

&lt;p&gt;Take one change: an engineer removes a prop from a TypeScript component. On the next build, Auto Type Table renders the props table without it, while the quickstart's usage example still passes the removed prop. API pages built with &lt;code&gt;generateFiles()&lt;/code&gt; stay stale until someone runs the generation script again.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Fumadocs builds from your source
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://fumadocs.dev/docs/mdx" rel="noopener noreferrer"&gt;Fumadocs MDX compiles Markdown and MDX from a directory you configure (&lt;code&gt;content/docs&lt;/code&gt; in its examples) and works with Next.js or Vite&lt;/a&gt;, so your docs can live in the same repository as the app they describe. Two parts of its toolkit go further and write pages straight from code.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://fumadocs.dev/docs/integrations/openapi" rel="noopener noreferrer"&gt;fumadocs-openapi integration&lt;/a&gt; gives you two ways to turn a spec into pages. &lt;code&gt;generateFiles()&lt;/code&gt; writes MDX files to disk, and you refresh them by running a script again. &lt;code&gt;staticSource()&lt;/code&gt; writes no files: it "generates pages directly to your &lt;code&gt;loader()&lt;/code&gt;, hence it allows dynamic content generation, such as re-generating page tree as schema changes."&lt;/p&gt;

&lt;p&gt;For components, &lt;a href="https://fumadocs.dev/docs/ui/components/auto-type-table" rel="noopener noreferrer"&gt;Auto Type Table&lt;/a&gt; "generates a table for your docs based on TypeScript definitions." It comes from the &lt;a href="https://fumadocs.dev/docs/integrations/typescript" rel="noopener noreferrer"&gt;fumadocs-typescript integration&lt;/a&gt;, and it "relies on the file system, hence, the page referencing this component must be built in build time."&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%2Fic55mxl18zwkb26yh0d3.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%2Fic55mxl18zwkb26yh0d3.png" alt="Diagram: pages generated from code (Auto Type Table, OpenAPI pages) are true by construction, while hand-written guides and usage examples need an update loop, where EkLine drafts updates as pull requests" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where a Fumadocs site still drifts
&lt;/h2&gt;

&lt;p&gt;Test it with your own history: find the latest merged pull request that removed a prop or changed an endpoint. Did anything flag the guide that still uses the old one? Here is what each tool reads:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Native check&lt;/th&gt;
&lt;th&gt;What it verifies&lt;/th&gt;
&lt;th&gt;What it does not see&lt;/th&gt;
&lt;th&gt;Where EkLine fits&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://fumadocs.dev/docs/ui/components/auto-type-table" rel="noopener noreferrer"&gt;Auto Type Table&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Props tables built from TypeScript types at build time&lt;/td&gt;
&lt;td&gt;Usage examples typed into MDX&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://docs.ekline.io/agent/automatic-pr-review/" rel="noopener noreferrer"&gt;Automatic PR review&lt;/a&gt; reads the code pull request and can draft the example fix&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://fumadocs.dev/docs/integrations/openapi" rel="noopener noreferrer"&gt;fumadocs-openapi&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;API pages from the spec, live with &lt;code&gt;staticSource()&lt;/code&gt; or on rerun with &lt;code&gt;generateFiles()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Guides that call those endpoints&lt;/td&gt;
&lt;td&gt;Docs Agent edits that hand-written MDX in your &lt;a href="https://docs.ekline.io/agent/github-app-setup/" rel="noopener noreferrer"&gt;docs repository&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://fumadocs.dev/docs/mdx" rel="noopener noreferrer"&gt;Fumadocs MDX&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;That your Markdown and MDX compile&lt;/td&gt;
&lt;td&gt;Whether a compiled sentence is still true&lt;/td&gt;
&lt;td&gt;Docs Agent drafts the correction, and Docs Reviewer checks the MDX in CI, without a &lt;a href="https://docs.ekline.io/reviewer/configuration/framework-support/" rel="noopener noreferrer"&gt;Fumadocs framework value&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Generated files are only as fresh as their last run. In March 2026, piersonmarks opened a &lt;a href="https://github.com/fuma-nama/fumadocs/issues/3155" rel="noopener noreferrer"&gt;feature request for an OpenAPI watch mode&lt;/a&gt; and described the gap: "The &lt;code&gt;fumadocs-mdx&lt;/code&gt; bundler watches for MDX file changes, but nothing watches the upstream OpenAPI spec that &lt;em&gt;produces&lt;/em&gt; those MDX files." The maintainer closed the request and replied that, because schemas can also come from URLs and custom functions, a watcher written in your own project is simpler and more flexible than a built-in wrapper. On a &lt;code&gt;generateFiles()&lt;/code&gt; setup, keeping the reference current is your job.&lt;/p&gt;

&lt;p&gt;Auto Type Table has its own failure case: the docs warn that "Rendering the component on serverless runtime may cause problems," and its setup example sets a cache for serverless platforms.&lt;/p&gt;

&lt;p&gt;The bigger gap sits outside generation. In the removed-prop example, the table corrects itself and the usage example does not, because a person typed it. The same holds for the quickstart prose about the prop and the migration note that should name its replacement. The Fumadocs MDX documentation describes no built-in AI writer that would draft those edits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not generate everything?
&lt;/h2&gt;

&lt;p&gt;The strongest answer to drift is to generate more, and on Fumadocs it goes far. If an endpoint or a prop can be described from the spec or the type, generate it and stop maintaining it by hand.&lt;/p&gt;

&lt;p&gt;Generation still stops at the type. A type says which props exist. It does not say which two props you combine for a common layout, why the removed prop went away, or what a reader on the old version should change before upgrading. Guides, migration notes, and worked examples carry that knowledge, and none of it can be generated from types.&lt;/p&gt;

&lt;p&gt;So generation plus a same-PR rule is enough when the docs sit in the app repository and every fact the guide needs is in that pull request. It falls short when the facts come from a ticket, Slack, a spec, or another repository; in an &lt;a href="https://news.ycombinator.com/item?id=48825447" rel="noopener noreferrer"&gt;Ask HN thread&lt;/a&gt; on the subject, practitioners write docs first or script nightly passes.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Fumadocs sync checklist for this week
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Prefer &lt;code&gt;staticSource()&lt;/code&gt; when your spec changes often, so the page tree follows the schema instead of a file someone forgot to regenerate.&lt;/li&gt;
&lt;li&gt;Where you need &lt;code&gt;generateFiles()&lt;/code&gt; (for example, to edit the generated MDX), run the generation script in CI on every change to the spec, and fail the job when the committed files differ from fresh output.&lt;/li&gt;
&lt;li&gt;Use Auto Type Table for every public component, build those pages at build time, and set the cache the docs ask for if you deploy to a serverless host.&lt;/li&gt;
&lt;li&gt;Keep a list of hand-written pages that mention a generated thing (a prop, an endpoint, a type name), and review that list as part of every release.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Adding EkLine to a Fumadocs repository
&lt;/h2&gt;

&lt;p&gt;EkLine adds the update loop for the hand-written half.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;On a Fumadocs site&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Where your content lives&lt;/td&gt;
&lt;td&gt;MDX in &lt;code&gt;content/docs&lt;/code&gt;, inside the Next.js app repository or in a separate docs repository, on GitHub or GitLab.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What EkLine watches&lt;/td&gt;
&lt;td&gt;The &lt;a href="https://docs.ekline.io/agent/github-app-setup/" rel="noopener noreferrer"&gt;GitHub App&lt;/a&gt; separates documentation repositories, where you set the docs path, from code repositories, which it "reviews for documentation impact." Your app repository can be the code repository.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where the pull request lands&lt;/td&gt;
&lt;td&gt;A docs pull request (a merge request on GitLab) that edits hand-written MDX. Generated pages stay with Fumadocs; say so in &lt;a href="https://docs.ekline.io/agent/custom-instructions/" rel="noopener noreferrer"&gt;custom instructions&lt;/a&gt;, which hold up to 5,000 characters and apply to every new session.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Which CI check runs&lt;/td&gt;
&lt;td&gt;Docs Reviewer checks MDX for style, consistency, links, and accuracy. Its &lt;a href="https://docs.ekline.io/reviewer/configuration/framework-support/" rel="noopener noreferrer"&gt;framework values&lt;/a&gt; cover mintlify, astro, fern, docusaurus, and gitbook, with no Fumadocs value, and the &lt;a href="https://docs.ekline.io/reviewer/integrations/cli-integration/" rel="noopener noreferrer"&gt;CLI docs&lt;/a&gt; note that "Without framework awareness, EkLine might skip content inside these components."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Which Scheduled Agents check fits&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://docs.ekline.io/agent/scheduled-agents/" rel="noopener noreferrer"&gt;Code Sample Review&lt;/a&gt;, which "Verifies that code samples in your docs are accurate and runnable."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What stays in Fumadocs&lt;/td&gt;
&lt;td&gt;OpenAPI pages, Auto Type Table, the page tree, and your Next.js build and deploy.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In the removed-prop example, when the engineer opens the pull request (or moves it out of draft), &lt;a href="https://docs.ekline.io/agent/automatic-pr-review/" rel="noopener noreferrer"&gt;automatic PR review&lt;/a&gt; reads the diff, title, and description once. A low-confidence result stays silent, a medium one posts a suggestion comment, and a high one opens a docs pull request that rewrites the quickstart example without the prop. The props table needs no edit, because the next build regenerates it. GitLab merge requests get the same review &lt;a href="https://docs.ekline.io/changelog/" rel="noopener noreferrer"&gt;since May 2026&lt;/a&gt;. If the change merged before EkLine was connected or scored below the threshold, the weekly &lt;a href="https://docs.ekline.io/agent/prevent-documentation-drift/" rel="noopener noreferrer"&gt;Docs Drift Review&lt;/a&gt; audits the last 30 days of GitHub activity and fixes the highest-priority gap. Someone on your team reviews and merges every one of these pull requests.&lt;/p&gt;

&lt;p&gt;Will engineers review docs instead of writing them? P0 Security, whose docs are hosted on GitBook, reports "15 integration guides shipped, zero new headcount" with EkLine, and its engineers spent their time &lt;a href="https://ekline.io/case-study/how-p0-securitys-engineering-team-stopped-writing-docs-and-started-shipping-faster" rel="noopener noreferrer"&gt;"reviewing documentation instead of writing it."&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  If you move off Fumadocs one day
&lt;/h2&gt;

&lt;p&gt;Keep the docs as MDX in Git, and the move costs the least of your accuracy work: the update loop for hand-written pages keeps working. &lt;a href="https://potluck.ekline.io" rel="noopener noreferrer"&gt;Potluck Docs&lt;/a&gt;, which EkLine maintains, is one option: an &lt;a href="https://github.com/ekline-io/potluck-docs" rel="noopener noreferrer"&gt;MIT-licensed Astro + Starlight template&lt;/a&gt; whose OpenAPI references are rendered by Scalar with a client for sending real requests. It adds private and org-scoped docs, and it generates a sitemap and &lt;code&gt;llms.txt&lt;/code&gt; on every build. It calls itself "a template, not a hosted product."&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does EkLine replace your docs platform?
&lt;/h3&gt;

&lt;p&gt;No. Fumadocs keeps generating your OpenAPI pages and type tables, and your Next.js or Vite build keeps rendering and deploying the site. EkLine works on the MDX you write by hand: Docs Agent drafts updates as pull requests when code changes, Docs Reviewer checks the MDX in CI, and Scheduled Agents review code samples. Your team merges every change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can Fumadocs regenerate OpenAPI pages automatically?
&lt;/h3&gt;

&lt;p&gt;With &lt;code&gt;staticSource()&lt;/code&gt;, yes: the fumadocs-openapi docs say it generates pages directly to your &lt;code&gt;loader()&lt;/code&gt;, which allows re-generating the page tree as the schema changes. With &lt;code&gt;generateFiles()&lt;/code&gt;, you run the generation script again after each spec change. A March 2026 request for a built-in watch mode was closed, and the maintainer suggested a watcher in your own project, so most teams also run the script in CI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Auto Type Table work on serverless hosting like Vercel?
&lt;/h3&gt;

&lt;p&gt;Build the pages that use it at build time. The Auto Type Table docs say the component relies on the file system, warn that rendering it on a serverless runtime may cause problems, and its setup example sets a cache for serverless platforms such as Vercel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can't we script this ourselves?
&lt;/h3&gt;

&lt;p&gt;For the generated half, the maintainer's answer to the watch-mode request was a watcher in your own project. For the hand-written half, cyanydeez described "a for loop that runs nightly on any files updated the prior day," letting local AI revise the Markdown file next to each one. A quickstart in content/docs sits apart from the component it uses. Every rewrite still needs checking, the part rstagi called hard: "it's hard to fact-check everything all the time." EkLine drafts the guide edit as a pull request from the code change, and your team decides.&lt;/p&gt;

&lt;p&gt;Pick the last pull request that removed a prop or changed an endpoint, and bring it. Docs Agent is available on all plans, with access granted on request. &lt;a href="https://ekline.io" rel="noopener noreferrer"&gt;Book a demo to see what EkLine drafts for the hand-written half of your Fumadocs site&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>documentation</category>
      <category>nextjs</category>
      <category>typescript</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How to keep Docusaurus docs in sync with your source code</title>
      <dc:creator>Alisa Hester</dc:creator>
      <pubDate>Mon, 12 Oct 2026 14:23:00 +0000</pubDate>
      <link>https://dev.to/alisa_hester_1/how-to-keep-docusaurus-docs-in-sync-with-your-source-code-4f03</link>
      <guid>https://dev.to/alisa_hester_1/how-to-keep-docusaurus-docs-in-sync-with-your-source-code-4f03</guid>
      <description>&lt;p&gt;Docusaurus keeps docs in sync with source code through the repository. It gives you three real sync tools: a monorepo, code blocks imported from files, and a build that fails on broken links. All three assume the code lives next to the docs. Answering that exact question in &lt;a href="https://github.com/facebook/docusaurus/discussions/3722" rel="noopener noreferrer"&gt;Docusaurus discussion #3722&lt;/a&gt; in November 2020, a maintainer said that with docs and code in separate repositories, keeping them in sync is your job.&lt;/p&gt;

&lt;p&gt;Your Docusaurus site lives in its own docs repository, and your SDK lives in another. An engineer renames a configuration option in the SDK. Every guide that names the old option is now wrong, and no Docusaurus tool flags it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Docusaurus already does to keep docs and code together
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://docusaurus.io/docs/installation" rel="noopener noreferrer"&gt;installation guide&lt;/a&gt; suggests a monorepo when you document an existing project: "Then, your contributors can update the docs as they implement features."&lt;/p&gt;

&lt;p&gt;Inside one repository, you can import a source file as raw text with &lt;a href="https://docusaurus.io/docs/markdown-features/react" rel="noopener noreferrer"&gt;raw-loader and render it with Docusaurus's CodeBlock component&lt;/a&gt;. The docs add a warning: "This feature is experimental and might be subject to breaking API changes in the future."&lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://docusaurus.io/docs/api/docusaurus-config" rel="noopener noreferrer"&gt;configuration reference&lt;/a&gt;, &lt;code&gt;onBrokenLinks&lt;/code&gt; defaults to &lt;code&gt;throw&lt;/code&gt;, so a link to a moved or deleted page fails the build. It has two limits: "The broken links detection is only available for a production build (&lt;code&gt;docusaurus build&lt;/code&gt;)." And &lt;code&gt;onBrokenAnchors&lt;/code&gt; defaults to &lt;code&gt;warn&lt;/code&gt;, so a dead heading link ships with a warning.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docusaurus.io/docs/versioning" rel="noopener noreferrer"&gt;Versioning&lt;/a&gt; copies the full &lt;code&gt;docs/&lt;/code&gt; folder into &lt;code&gt;versioned_docs/version-[versionName]/&lt;/code&gt; when you create a version, and each copy is edited independently. The docs advise against versioning unless you need it, because it adds build time and complexity.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/PaloAltoNetworks/docusaurus-openapi-docs" rel="noopener noreferrer"&gt;docusaurus-openapi-docs plugin&lt;/a&gt; generates MDX "using the OpenAPI specification as the source." Palo Alto Networks maintains it under the MIT license, outside the official Docusaurus project.&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%2Fazyychlyddmsrdxv56wr.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%2Fazyychlyddmsrdxv56wr.png" alt="Diagram: an SDK repo with a renamed option and a separate Docusaurus docs repo whose code imports and broken-link check stop at the repo edge; EkLine bridges the two by reading the change and opening a docs pull request" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the renamed option slips through
&lt;/h2&gt;

&lt;p&gt;Try it on your own site. Open the last merged SDK pull request that renamed an option. Did anything flag the guide that still names the old one? Each Docusaurus check answers a narrower question:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Native check&lt;/th&gt;
&lt;th&gt;What it verifies&lt;/th&gt;
&lt;th&gt;What it does not see&lt;/th&gt;
&lt;th&gt;Where EkLine fits&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://docusaurus.io/docs/api/docusaurus-config" rel="noopener noreferrer"&gt;&lt;code&gt;onBrokenLinks&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Links to moved or deleted pages, in production builds&lt;/td&gt;
&lt;td&gt;A guide that names a renamed option with every link intact&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://docs.ekline.io/agent/automatic-pr-review/" rel="noopener noreferrer"&gt;Automatic PR review&lt;/a&gt; treats "renamed configuration" as a high-confidence case&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://docusaurus.io/docs/markdown-features/react" rel="noopener noreferrer"&gt;raw-loader imports&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Code blocks that match a file in the docs checkout&lt;/td&gt;
&lt;td&gt;Code in a separate SDK repository&lt;/td&gt;
&lt;td&gt;The &lt;a href="https://docs.ekline.io/agent/github-app-setup/" rel="noopener noreferrer"&gt;GitHub App&lt;/a&gt; watches the SDK repository and writes to the docs repository&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/PaloAltoNetworks/docusaurus-openapi-docs" rel="noopener noreferrer"&gt;docusaurus-openapi-docs&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Reference MDX generated from your spec&lt;/td&gt;
&lt;td&gt;Guides and samples written around it&lt;/td&gt;
&lt;td&gt;The Code Sample Review agent in &lt;a href="https://docs.ekline.io/agent/scheduled-agents/" rel="noopener noreferrer"&gt;Scheduled Agents&lt;/a&gt; checks those samples&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The raw-loader example imports a relative path such as &lt;code&gt;./myComponent&lt;/code&gt;, so the file has to be reachable from the docs project, which in practice means it sits in the same checkout. A renamed option breaks no link, so &lt;code&gt;onBrokenLinks&lt;/code&gt; has nothing to throw on. The build passes, and the guide still tells readers to set the old name.&lt;/p&gt;

&lt;p&gt;With versioning on, the page in &lt;code&gt;docs/&lt;/code&gt; belongs to the current version, the frozen copy under &lt;code&gt;versioned_docs/&lt;/code&gt; also names the old option, and each copy changes only when someone edits it.&lt;/p&gt;

&lt;p&gt;A build can also fail without failing your deploy. In a &lt;a href="https://github.com/facebook/docusaurus/issues/11890" rel="noopener noreferrer"&gt;Docusaurus 3.9.2 issue&lt;/a&gt; about an &lt;code&gt;.md&lt;/code&gt; and an &lt;code&gt;.mdx&lt;/code&gt; file sharing one name, josh-wong reported that "a site with build errors still goes live instead of failing the deployment." The maintainer traced it to a custom &lt;code&gt;build&lt;/code&gt; script whose pipe to &lt;code&gt;tee&lt;/code&gt; made it exit with code 0.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Docusaurus sync checklist for this week
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Move the docs into the code repository if your team can, so the guide edit rides in the same pull request as the rename.&lt;/li&gt;
&lt;li&gt;Replace pasted snippets with raw-loader imports wherever the code shares the docs repository, and treat the experimental label as an upgrade risk.&lt;/li&gt;
&lt;li&gt;Run a full &lt;code&gt;docusaurus build&lt;/code&gt; on every docs pull request, since link checks run only in production builds, and consider setting &lt;code&gt;onBrokenAnchors&lt;/code&gt; to &lt;code&gt;throw&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Confirm that your build script exits non-zero on a Docusaurus error (a pipe to &lt;code&gt;tee&lt;/code&gt; can hide the exit code), so your deploy job stops.&lt;/li&gt;
&lt;li&gt;After each SDK release, search &lt;code&gt;docs/&lt;/code&gt; and every &lt;code&gt;versioned_docs/&lt;/code&gt; folder for renamed options, and decide per version whether the frozen copy changes.&lt;/li&gt;
&lt;li&gt;Generate API reference pages from your OpenAPI spec with the community plugin, so review time goes to the guides.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Should you move the docs into the SDK monorepo?
&lt;/h2&gt;

&lt;p&gt;For most teams it is the best native answer, and the Docusaurus installation guide suggests it for documenting an existing project: the guide sits in the same pull request as the rename. It still leaves the prose to whoever opens the pull request. The Docusaurus docs we reviewed describe no check that compares a guide's sentences with the code, and a rename's reason often sits in a ticket or Slack thread outside the pull request.&lt;/p&gt;

&lt;p&gt;Here is the line. A monorepo plus a same-PR rule is enough when the pull request carries every fact the guide needs; that is the shared-repository case the maintainer described in &lt;a href="https://github.com/facebook/docusaurus/discussions/3722" rel="noopener noreferrer"&gt;discussion #3722&lt;/a&gt;. It stops being enough once a fact lives in a ticket, a Slack thread, a spec, or the separate SDK repository. Docs-first habits and nightly scripts are what practitioners describe in an &lt;a href="https://news.ycombinator.com/item?id=48825447" rel="noopener noreferrer"&gt;Ask HN thread&lt;/a&gt; on keeping docs current.&lt;/p&gt;

&lt;h2&gt;
  
  
  How EkLine plugs into a Docusaurus repository
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;On a Docusaurus site&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Where your content lives&lt;/td&gt;
&lt;td&gt;Your docs repository, added in the &lt;a href="https://docs.ekline.io/agent/github-app-setup/" rel="noopener noreferrer"&gt;EkLine GitHub App&lt;/a&gt; as a Documentation Repository, with paths such as &lt;code&gt;docs/&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What EkLine watches&lt;/td&gt;
&lt;td&gt;Your SDK and service repositories, added separately as Code Repositories: "Source code repositories that EkLine reviews for documentation impact"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;When it reads a change&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://docs.ekline.io/agent/automatic-pr-review/" rel="noopener noreferrer"&gt;Automatic PR review&lt;/a&gt; assesses each pull request once, when it opens or leaves draft; a weekly &lt;a href="https://docs.ekline.io/agent/prevent-documentation-drift/" rel="noopener noreferrer"&gt;Docs Drift Review&lt;/a&gt; audits the last 30 days of GitHub activity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where its pull request lands&lt;/td&gt;
&lt;td&gt;Your docs repository, opened by &lt;a href="https://docs.ekline.io/agent/update-review/" rel="noopener noreferrer"&gt;Docs Agent&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Which CI check runs&lt;/td&gt;
&lt;td&gt;Docs Reviewer, through the &lt;a href="https://docs.ekline.io/reviewer/quickstart/github-action/" rel="noopener noreferrer"&gt;EkLine GitHub Action&lt;/a&gt;, posts inline comments on the pull request, covering style, consistency, links, and accuracy; Docs Reviewer accepts a &lt;code&gt;docusaurus&lt;/code&gt; &lt;a href="https://docs.ekline.io/reviewer/configuration/framework-support/" rel="noopener noreferrer"&gt;framework value&lt;/a&gt;, and "Support for other frameworks is expanding."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What stays in Docusaurus&lt;/td&gt;
&lt;td&gt;The build, theme, plugins, versioning, previews, and hosting&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When the engineer opens the SDK pull request, automatic PR review lists "renamed configuration" among its high-confidence cases, which "Posts a comment that automatically triggers the docs update flow." Docs Agent drafts the update as a pull request in the docs repository, where your build runs as usual and a reviewer merges or closes it. The assessment reads only the diff, title, and description; when the reason for the rename sits in a Jira ticket or a Slack thread, you can &lt;a href="https://docs.ekline.io/agent/combine-sources/" rel="noopener noreferrer"&gt;combine those sources&lt;/a&gt; in a Docs Agent session and get one pull request.&lt;/p&gt;

&lt;p&gt;For snippets that raw-loader cannot reach, use the Code Sample Review agent in &lt;a href="https://docs.ekline.io/agent/scheduled-agents/" rel="noopener noreferrer"&gt;Scheduled Agents&lt;/a&gt;, which "Verifies that code samples in your docs are accurate and runnable." Docs Agent works with GitHub and GitLab and is available to all plans, with access granted on request.&lt;/p&gt;

&lt;p&gt;Worried about engineering time? At &lt;a href="https://ekline.io/case-study/how-p0-securitys-engineering-team-stopped-writing-docs-and-started-shipping-faster" rel="noopener noreferrer"&gt;P0 Security&lt;/a&gt;, which hosts its docs on GitBook, "In 6 months, engineers reclaimed 150+ hours of product development time by reviewing documentation instead of writing it."&lt;/p&gt;

&lt;h2&gt;
  
  
  If you are starting fresh or leaving Docusaurus
&lt;/h2&gt;

&lt;p&gt;If you do leave, the target that puts the least of your accuracy work at risk keeps your docs as Markdown in Git, because the same pull request loop then works on the new repository. &lt;a href="https://potluck.ekline.io" rel="noopener noreferrer"&gt;Potluck Docs&lt;/a&gt; is one such target: an &lt;a href="https://github.com/ekline-io/potluck-docs" rel="noopener noreferrer"&gt;MIT-licensed Astro + Starlight template&lt;/a&gt; maintained by EkLine, with OpenAPI references "rendered by Scalar with a built-in client for sending real requests," private docs behind your SSO, org-scoped docs, and a sitemap and &lt;code&gt;llms.txt&lt;/code&gt; generated on every build. It is "a template, not a hosted product." Nothing about EkLine requires the move. Compare &lt;a href="https://ekline.io/blog/ekline-vs-docusaurus" rel="noopener noreferrer"&gt;EkLine vs Docusaurus&lt;/a&gt;, or see &lt;a href="https://ekline.io/blog/docusaurus-alternatives" rel="noopener noreferrer"&gt;Docusaurus alternatives&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does EkLine replace your docs platform?
&lt;/h3&gt;

&lt;p&gt;No. Docusaurus keeps building, versioning, and hosting your site. EkLine adds Docs Agent pull requests in your Docusaurus repository, drafted from changes in your SDK repositories, and Docs Reviewer comments in CI.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does Docusaurus sync with the source code?
&lt;/h3&gt;

&lt;p&gt;Through a shared repository. In discussion #3722, a maintainer replied that code and docs in the same Git repository "remain in sync automatically," and that with separate repositories "you have the responsibility to maintain sync." He linked an example of importing a code block from a source file. EkLine covers that separate-repository case.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can Docusaurus import code from a file?
&lt;/h3&gt;

&lt;p&gt;Yes, from a file the build can reach. Import it as raw text with raw-loader and render it with Docusaurus's CodeBlock component. The feature is marked experimental, and the import is a relative path, so in practice the file sits in the docs checkout.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can't we script this ourselves?
&lt;/h3&gt;

&lt;p&gt;You can script part of it. In the Ask HN thread, cyanydeez described "a for loop that runs nightly on any files updated the prior day," and has local AI rewrite the Markdown file that sits next to each one. That pairing assumes the doc sits beside the code, the shared-repository case from discussion #3722; your guides live in a separate Docusaurus repository, and versioned copies multiply them. rstagi named the remaining cost: "it's hard to fact-check everything all the time." EkLine reads the SDK pull request and, when the impact is clear, opens a docs pull request your team reviews.&lt;/p&gt;

&lt;p&gt;Bring the last SDK pull request that renamed an option. &lt;a href="https://ekline.io" rel="noopener noreferrer"&gt;Book a demo and walk an SDK change through to a Docusaurus pull request.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>documentation</category>
      <category>javascript</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to keep VitePress docs in sync with your code</title>
      <dc:creator>Alisa Hester</dc:creator>
      <pubDate>Mon, 12 Oct 2026 14:20:01 +0000</pubDate>
      <link>https://dev.to/alisa_hester_1/how-to-keep-vitepress-docs-in-sync-with-your-code-16b2</link>
      <guid>https://dev.to/alisa_hester_1/how-to-keep-vitepress-docs-in-sync-with-your-code-16b2</guid>
      <description>&lt;p&gt;To keep VitePress docs in sync with your code, import every code sample from your source files and leave dead-link checking on, so the build fails when a linked page goes missing and, on the VitePress 2.0 alpha line, when an imported file goes missing. Then review the sentences next to those samples on each release, because no VitePress setting checks that prose against the code.&lt;/p&gt;

&lt;p&gt;Say a function's default value changes. The snippet you import with VitePress's code-snippet directive shows the new code on the next build. The paragraph above it still explains the old default, and the page's last-updated date looks recent because someone fixed a typo last week.&lt;/p&gt;

&lt;h2&gt;
  
  
  What VitePress keeps in sync for you
&lt;/h2&gt;

&lt;p&gt;Its &lt;a href="https://vitepress.dev/guide/getting-started" rel="noopener noreferrer"&gt;getting started guide&lt;/a&gt; says "Markdown files outside the &lt;code&gt;.vitepress&lt;/code&gt; directory are considered source files." These tools act on those files, as described in the 2.0.0-alpha.20 docs. The stable release on npm is still the 1.6.x line, and missing-file handling differs between the two, as noted below.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code snippet imports. The &lt;a href="https://vitepress.dev/guide/markdown" rel="noopener noreferrer"&gt;Markdown guide&lt;/a&gt; shows how its code-snippet directive pulls a code block from a file, where "The value of &lt;code&gt;@&lt;/code&gt; corresponds to the source root." Import one VS Code &lt;code&gt;#region&lt;/code&gt; instead of the whole file, and the page shows only the lines that matter.&lt;/li&gt;
&lt;li&gt;Markdown includes. The Markdown include directive inserts one Markdown file into another, with line ranges or a region, so shared steps live in one file.&lt;/li&gt;
&lt;li&gt;Missing-file errors. According to the same guide, "Importing a file or region that does not exist throws a build error," and "Including a missing file, region, heading anchor, or an out-of-range line selection throws a build error." On 2.0 alpha releases, rename a source file or delete a region marker and the build stops; setting &lt;code&gt;markdown.snippet.silent&lt;/code&gt; or &lt;code&gt;markdown.include.silent&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; turns that error into a warning. The &lt;a href="https://github.com/vuejs/vitepress/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;VitePress changelog&lt;/a&gt; for 2.0.0-alpha.17 notes that includes previously "silently ignored errors when files did not exist," so on the stable 1.6.x line, check the rendered output.&lt;/li&gt;
&lt;li&gt;Dead links. In the &lt;a href="https://vitepress.dev/reference/site-config" rel="noopener noreferrer"&gt;site config reference&lt;/a&gt;, &lt;code&gt;ignoreDeadLinks&lt;/code&gt; defaults to false, and the description reads: "When set to &lt;code&gt;true&lt;/code&gt;, VitePress will not fail builds due to dead links."&lt;/li&gt;
&lt;li&gt;Last-updated dates. &lt;code&gt;lastUpdated&lt;/code&gt; controls "Whether to get the last updated timestamp for each page using Git," and with the default theme, "enabling this option will display each page's last updated time."&lt;/li&gt;
&lt;/ul&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%2Fnxmziqvrt4oi15fug9f8.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%2Fnxmziqvrt4oi15fug9f8.png" alt="Diagram: a docs page where the code block imported from source is current but the paragraph above it explains the old default, with a recent last-updated date; a recent date does not mean an accurate page" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the prose falls behind
&lt;/h2&gt;

&lt;p&gt;Run this check against your repository. Open the newest merged pull request that changed a default, a parameter, or a return value. Did anything flag the paragraph that explains it? The import did its job, and the build passes because every file and region it references still exists. Nothing in that pipeline compares the sentence that names the default with the code below it, so the page now contradicts itself.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Native check&lt;/th&gt;
&lt;th&gt;What it verifies&lt;/th&gt;
&lt;th&gt;What it does not see&lt;/th&gt;
&lt;th&gt;Where EkLine fits&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vitepress.dev/guide/markdown" rel="noopener noreferrer"&gt;Code-snippet imports&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;The code block matches a file the build can read&lt;/td&gt;
&lt;td&gt;The sentence above the block, and code in another repository&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://docs.ekline.io/agent/automatic-pr-review/" rel="noopener noreferrer"&gt;Automatic PR review&lt;/a&gt; reads the code pull request and can draft the prose fix&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vitepress.dev/reference/site-config" rel="noopener noreferrer"&gt;&lt;code&gt;ignoreDeadLinks&lt;/code&gt; left off&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Markdown links point at pages that exist&lt;/td&gt;
&lt;td&gt;Sidebar entries, per &lt;a href="https://github.com/vuejs/vitepress/issues/3244" rel="noopener noreferrer"&gt;issue 3244&lt;/a&gt;, and prose that went stale&lt;/td&gt;
&lt;td&gt;Docs Reviewer checks links and writing rules on each pull request through the &lt;a href="https://docs.ekline.io/reviewer/quickstart/github-action/" rel="noopener noreferrer"&gt;GitHub Action&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vitepress.dev/reference/site-config" rel="noopener noreferrer"&gt;&lt;code&gt;lastUpdated&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;When a commit last touched the page&lt;/td&gt;
&lt;td&gt;Whether the page still matches the product&lt;/td&gt;
&lt;td&gt;A weekly &lt;a href="https://docs.ekline.io/agent/prevent-documentation-drift/" rel="noopener noreferrer"&gt;Docs Drift Review&lt;/a&gt; audits changes that merged without docs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Sidebar entries are a second gap. In a &lt;a href="https://github.com/vuejs/vitepress/issues/3244" rel="noopener noreferrer"&gt;VitePress issue titled "Dead links in sidebar not reported,"&lt;/a&gt; koebi wrote in November 2023 that a sidebar link to a missing page "builds even if Bar doesn't exist." The issue was still open when we checked in October 2026, so treat sidebar entries as unchecked.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;@&lt;/code&gt; alias resolves to the source root, so a snippet stays current only when the build can read the file; code in a separate repository is outside that build unless you check it out or copy it in. A sample pasted by hand from a separate SDK repository is a copy that nothing in the build compares with its original.&lt;/p&gt;

&lt;p&gt;That sets the boundary. Imports, the dead-link check, and a same-PR rule are enough when the code sits inside the docs build and every fact the paragraph needs is in the pull request. When the facts live in tickets, Slack threads, specs, or a separate SDK repository, nothing ties them to the page; an &lt;a href="https://news.ycombinator.com/item?id=48825447" rel="noopener noreferrer"&gt;Ask HN thread&lt;/a&gt; on keeping docs current collects docs-first habits and nightly scripts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does lastUpdated tell readers a page is current?
&lt;/h2&gt;

&lt;p&gt;A fair objection: &lt;code&gt;lastUpdated&lt;/code&gt; shows when a page last changed, so readers can judge its age for themselves. The date comes from Git, so it reflects a real commit and is honest metadata.&lt;/p&gt;

&lt;p&gt;But it records that a commit touched the file, which is a different fact from the page matching the product. In the example, the typo fix moved the date forward while the paragraph about the default stayed wrong. Show the date to readers, and keep it out of how you decide which pages need review.&lt;/p&gt;

&lt;h2&gt;
  
  
  A VitePress sync checklist for this week
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Find every hand-pasted code block that has a source file in the repository, and replace it with a VitePress code-snippet import of a &lt;code&gt;#region&lt;/code&gt; in code your tests already exercise.&lt;/li&gt;
&lt;li&gt;Confirm that &lt;code&gt;ignoreDeadLinks&lt;/code&gt; is unset or false in your site config, and, on 2.0 alpha, that no one has set &lt;code&gt;markdown.snippet.silent&lt;/code&gt; or &lt;code&gt;markdown.include.silent&lt;/code&gt;; on 1.6.x, open the rendered pages that import code.&lt;/li&gt;
&lt;li&gt;Check sidebar entries on their own: after any page rename or deletion, open each sidebar link in a local build or preview.&lt;/li&gt;
&lt;li&gt;Turn on &lt;code&gt;lastUpdated&lt;/code&gt; for readers, and track review status somewhere else.&lt;/li&gt;
&lt;li&gt;On each release, list the code changes that alter defaults, parameters, or behavior, and read the paragraphs around every imported snippet for those files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last item costs the most, because someone has to connect each code change to the right page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding EkLine to a VitePress repository
&lt;/h2&gt;

&lt;p&gt;EkLine works on that last item, and VitePress keeps building the site.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;On a VitePress site&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Where your content lives&lt;/td&gt;
&lt;td&gt;Markdown outside &lt;code&gt;.vitepress&lt;/code&gt;, in a GitHub or GitLab repository&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What EkLine watches&lt;/td&gt;
&lt;td&gt;Code repositories, which the &lt;a href="https://docs.ekline.io/agent/github-app-setup/" rel="noopener noreferrer"&gt;GitHub App&lt;/a&gt; lists apart from documentation repositories; GitLab merge requests too, per the &lt;a href="https://docs.ekline.io/changelog/" rel="noopener noreferrer"&gt;May 2026 changelog&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;When it reads a change&lt;/td&gt;
&lt;td&gt;Each pull request &lt;a href="https://docs.ekline.io/agent/automatic-pr-review/" rel="noopener noreferrer"&gt;when it opens or leaves draft&lt;/a&gt;, once; a weekly &lt;a href="https://docs.ekline.io/agent/prevent-documentation-drift/" rel="noopener noreferrer"&gt;Docs Drift Review&lt;/a&gt; sweeps what already merged&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where its pull request lands&lt;/td&gt;
&lt;td&gt;Your docs repository, as a &lt;a href="https://docs.ekline.io/agent/update-review/" rel="noopener noreferrer"&gt;pull request&lt;/a&gt; your team reviews&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Which CI check runs&lt;/td&gt;
&lt;td&gt;Docs Reviewer, which checks each docs change for style, consistency, links, and accuracy, through the &lt;a href="https://docs.ekline.io/reviewer/quickstart/github-action/" rel="noopener noreferrer"&gt;GitHub Action&lt;/a&gt;; its &lt;a href="https://docs.ekline.io/reviewer/configuration/framework-support/" rel="noopener noreferrer"&gt;framework list&lt;/a&gt; has no VitePress value, so it reviews the Markdown without VitePress-specific handling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Samples that are not imported&lt;/td&gt;
&lt;td&gt;The Code Sample Review agent in &lt;a href="https://docs.ekline.io/agent/scheduled-agents/" rel="noopener noreferrer"&gt;Scheduled Agents&lt;/a&gt;, which "Verifies that code samples in your docs are accurate and runnable"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What stays in VitePress&lt;/td&gt;
&lt;td&gt;The build, imports and includes, the dead-link check, &lt;code&gt;lastUpdated&lt;/code&gt;, theme, and hosting&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In the example, the engineer opens the code pull request that changes the default. Docs Agent assesses it from the diff, title, and description; at high confidence it starts a docs pull request, and at medium confidence it leaves a suggestion comment. The draft rewrites the paragraph about the default and leaves the import line alone. You review and merge it, and the VitePress build runs as today. Automatic review reads the pull request alone, so for a change decided in a ticket, start a Docs Agent session from that ticket.&lt;/p&gt;

&lt;p&gt;Do reviewers end up rewriting what it drafts? At ContextQA, whose docs are hosted on GitBook, the team reviewed EkLine's updates from April 7 to July 6, 2026, and "Two-thirds merged as a single commit," which the &lt;a href="https://ekline.io/case-study/how-contextqas-engineers-merge-97-percent-of-what-an-ai-agent-writes" rel="noopener noreferrer"&gt;ContextQA case study&lt;/a&gt; reads as "the page was right the first time."&lt;/p&gt;

&lt;h2&gt;
  
  
  If your team leaves VitePress
&lt;/h2&gt;

&lt;p&gt;Should you move, pick a target that keeps the docs as Markdown in Git, because that way the pull request loop you build here keeps working after the move. &lt;a href="https://potluck.ekline.io" rel="noopener noreferrer"&gt;Potluck Docs&lt;/a&gt; is one such target, an &lt;a href="https://github.com/ekline-io/potluck-docs" rel="noopener noreferrer"&gt;open-source Astro + Starlight template&lt;/a&gt; under the MIT license that EkLine maintains. Its OpenAPI references render with Scalar and include a client for real requests; private docs and org-scoped docs are built in; and each build writes a sitemap and &lt;code&gt;llms.txt&lt;/code&gt;. The project describes itself as "a template, not a hosted product."&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does EkLine replace your docs platform?
&lt;/h3&gt;

&lt;p&gt;No. VitePress still builds and serves your site, and its imports and link checks keep working. EkLine adds the prose check: Docs Agent drafts Markdown updates from code pull requests and opens them in your docs repository for your team to approve.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does VitePress fail the build on dead links?
&lt;/h3&gt;

&lt;p&gt;Yes, for links in your Markdown. &lt;code&gt;ignoreDeadLinks&lt;/code&gt; defaults to false, and setting it to true stops VitePress from failing builds on dead links. Sidebar links are a separate case: a 2023 issue reports one to a missing page that still builds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can't we script this ourselves?
&lt;/h3&gt;

&lt;p&gt;Partly, since the imports already keep code blocks current. For the prose, cyanydeez described "a for loop that runs nightly on any files updated the prior day," and has local AI update the Markdown file beside it. On VitePress, the paragraph that explains a default sits on a page that imports the source file, not beside it. Each nightly rewrite needs a reader, which rstagi summed up as "it's hard to fact-check everything all the time." EkLine drafts that paragraph fix as a pull request from the code change, and your team approves it.&lt;/p&gt;

&lt;p&gt;Choose one pull request from your last release that changed a default, and bring it. To see Docs Agent draft prose fixes on your own VitePress repository, &lt;a href="https://ekline.io" rel="noopener noreferrer"&gt;book a demo&lt;/a&gt;. Docs Agent is available to all plans, and access is granted on request.&lt;/p&gt;

</description>
      <category>documentation</category>
      <category>vue</category>
      <category>vite</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Material for MkDocs maintenance mode: keep your docs accurate now</title>
      <dc:creator>Alisa Hester</dc:creator>
      <pubDate>Mon, 12 Oct 2026 06:46:01 +0000</pubDate>
      <link>https://dev.to/alisa_hester_1/material-for-mkdocs-maintenance-mode-keep-your-docs-accurate-now-5ck3</link>
      <guid>https://dev.to/alisa_hester_1/material-for-mkdocs-maintenance-mode-keep-your-docs-accurate-now-5ck3</guid>
      <description>&lt;p&gt;"Material for MkDocs is in maintenance mode and MkDocs 2.0 won't support it. What do I do with my docs now?" As of October 8, 2026, you can stay on MkDocs 1.x for now, wait for Zensical, or move. None of those choices makes a stale page true, so audit and fix accuracy first: stale pages migrate with you, and the fixes count under any choice.&lt;/p&gt;

&lt;p&gt;An engineer renames an argument on a Python function. mkdocstrings regenerates the API reference from the docstring, but the tutorial that calls the function with the old argument stays wrong. The same week, someone moves a file that a pymdownx snippet pulls from; the build passes and the snippet is gone from the page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the MkDocs stack stands on October 8, 2026
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Material for MkDocs: on November 5, 2025, its team announced that &lt;a href="https://squidfunk.github.io/mkdocs-material/blog/2025/11/05/zensical" rel="noopener noreferrer"&gt;"Material for MkDocs is in maintenance mode,"&lt;/a&gt; with support "for at least the next 12 months, fixing critical bugs and security vulnerabilities as needed."&lt;/li&gt;
&lt;li&gt;MkDocs 2.0: the Material team's post (February 18, 2026, updated October 2, 2026) says MkDocs 2.0 was &lt;a href="https://squidfunk.github.io/mkdocs-material/blog/2026/02/18/mkdocs-2.0/" rel="noopener noreferrer"&gt;"published as a pre-release on PyPI"&lt;/a&gt; in August 2026, that "No official release date has been announced yet," and that "MkDocs 2.0 won't have a plugin system." Of its own theme, the team writes that "it will cease to work with MkDocs 2.0."&lt;/li&gt;
&lt;li&gt;Zensical: &lt;a href="https://github.com/zensical/zensical/releases/tag/v0.0.68" rel="noopener noreferrer"&gt;version 0.0.68&lt;/a&gt; shipped on October 5 and &lt;a href="https://pypi.org/project/zensical/" rel="noopener noreferrer"&gt;0.0.69 followed on October 8&lt;/a&gt;, both classified as alpha on PyPI. Its &lt;a href="https://zensical.org/roadmap/" rel="noopener noreferrer"&gt;roadmap&lt;/a&gt; banner announces a "November 5 Zensical 0.1.0, Studio, and Spark Community launch," and the roadmap says "Our roadmap is directional and does not promise delivery dates." Its &lt;a href="https://zensical.org/compatibility/" rel="noopener noreferrer"&gt;compatibility page&lt;/a&gt; says "Zensical can build existing MkDocs projects without requiring changes."&lt;/li&gt;
&lt;li&gt;mkdocstrings: the &lt;a href="https://mkdocstrings.github.io/" rel="noopener noreferrer"&gt;project homepage&lt;/a&gt;, checked October 8, 2026, warns "mkdocstrings is in maintenance mode!"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To stay on 1.x is one team's choice; FeldrinH wrote on April 6, 2026, &lt;a href="https://github.com/squidfunk/mkdocs-material/issues/8581" rel="noopener noreferrer"&gt;"We are using MkDocs 1.6 and for the time being have no plans or bandwidth to migrate."&lt;/a&gt; Waiting for Zensical trades an alpha release and a directional roadmap for keeping your &lt;code&gt;mkdocs.yml&lt;/code&gt; and Markdown, with plugins checked against Zensical's replacement list. If you move, the target that risks least of your accuracy work keeps your docs as Markdown in Git, because the fixes you make now, and the pull request loop around them, move with the files. &lt;a href="https://github.com/ekline-io/potluck-docs" rel="noopener noreferrer"&gt;Potluck Docs&lt;/a&gt; is one: an MIT-licensed Astro and Starlight template maintained by EkLine. &lt;a href="https://potluck.ekline.io" rel="noopener noreferrer"&gt;Its site&lt;/a&gt; lists "Three example OpenAPI references, rendered by Scalar with a built-in client for sending real requests," private docs for signed-in readers and org-scoped docs for one org's members, and a sitemap and &lt;code&gt;llms.txt&lt;/code&gt; "generated on every build." It describes itself as "a template, not a hosted product," so hosting stays with you. For the side-by-side platform comparison, read our &lt;a href="https://ekline.io/blog/material-for-mkdocs-maintenance-mode" rel="noopener noreferrer"&gt;guide to Material for MkDocs maintenance mode&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2rjuf18fud6cylqw3w34.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%2F2rjuf18fud6cylqw3w34.png" alt="Diagram: three paths, stay on MkDocs 1.x, wait for Zensical, or move (for example to Potluck Docs), all resting on one foundation: accurate pages, audit and fix first" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What MkDocs and its extensions already check
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://www.mkdocs.org/user-guide/configuration/" rel="noopener noreferrer"&gt;&lt;code&gt;strict&lt;/code&gt; setting&lt;/a&gt;: "Set to &lt;code&gt;true&lt;/code&gt; to halt processing when a warning is raised." It defaults to false. In MkDocs 1.6, a missing file reports as a warning and a missing anchor only as info, so a strict build fails on a deleted page but passes a dead heading link until you raise anchor validation to warn.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://facelessuser.github.io/pymdown-extensions/extensions/snippets/" rel="noopener noreferrer"&gt;pymdownx Snippets&lt;/a&gt;: "an extension to insert markdown or HTML snippets into another markdown file." With line ranges, a page can show selected lines of a real file. With &lt;code&gt;check_paths&lt;/code&gt; at its default of off, a missing file means "the markup will be removed"; with it on, the build fails.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://mkdocstrings.github.io/" rel="noopener noreferrer"&gt;mkdocstrings&lt;/a&gt;: "Automatic documentation from sources, for MkDocs." Reference pages are rebuilt from docstrings, so signatures follow the code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Plugins lose their home in MkDocs 2.0. Snippets is, by our reading, a Markdown extension instead, but neither source says whether MkDocs 2.0 supports it, so test it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What still drifts on a Material site
&lt;/h2&gt;

&lt;p&gt;Run a test on your own site. Open the last merged pull request that renamed an argument or changed what a function does. Did anything flag the tutorial that calls it? On the next build, mkdocstrings shows the new signature, the strict build is green, and the tutorial still passes the old argument.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Native check&lt;/th&gt;
&lt;th&gt;What it verifies&lt;/th&gt;
&lt;th&gt;What it does not see&lt;/th&gt;
&lt;th&gt;Where EkLine fits&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.mkdocs.org/user-guide/configuration/" rel="noopener noreferrer"&gt;&lt;code&gt;strict&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Links to missing files, and anchors once you raise their level&lt;/td&gt;
&lt;td&gt;A tutorial whose links all resolve but whose call is out of date&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://docs.ekline.io/agent/automatic-pr-review/" rel="noopener noreferrer"&gt;Automatic PR review&lt;/a&gt; assesses the code pull request that renamed it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://facelessuser.github.io/pymdown-extensions/extensions/snippets/" rel="noopener noreferrer"&gt;Snippets&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Lines pulled from a real file, and with &lt;code&gt;check_paths&lt;/code&gt; on, that the file exists&lt;/td&gt;
&lt;td&gt;The prose around the snippet, and code in another repository&lt;/td&gt;
&lt;td&gt;Docs Agent drafts the prose fix as a &lt;a href="https://docs.ekline.io/agent/update-review/" rel="noopener noreferrer"&gt;Markdown pull request&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://mkdocstrings.github.io/" rel="noopener noreferrer"&gt;mkdocstrings&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Reference pages rebuilt from docstrings&lt;/td&gt;
&lt;td&gt;Tutorials and how-tos that call the function&lt;/td&gt;
&lt;td&gt;A weekly &lt;a href="https://docs.ekline.io/agent/prevent-documentation-drift/" rel="noopener noreferrer"&gt;Docs Drift Review&lt;/a&gt; sweeps changes that merged without docs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The moved snippet is quieter. With &lt;code&gt;check_paths&lt;/code&gt; off, Snippets removes the include and the page publishes with a hole where the example was.&lt;/p&gt;

&lt;p&gt;When is that enough? If your Python code and docs share a repository and the pull request holds every fact a tutorial needs, mkdocstrings plus a same-PR rule keeps pages right. If the facts sit in a ticket, a Slack thread, a spec, or another repository, the rule runs on memory, and the &lt;a href="https://news.ycombinator.com/item?id=48825447" rel="noopener noreferrer"&gt;Ask HN thread&lt;/a&gt; on keeping documentation current shows commenters relying on docs-first habits and nightly scripts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four checks to run on your MkDocs site this week
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Turn on &lt;code&gt;strict&lt;/code&gt; and raise anchor validation from info to warn, so broken headings fail the build alongside missing pages.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;check_paths&lt;/code&gt; to true for Snippets, so a moved source file stops the build instead of leaving a blank.&lt;/li&gt;
&lt;li&gt;List every plugin your site depends on: plugins don't carry to MkDocs 2.0, and each one needs a match in Zensical or in the framework you would move to.&lt;/li&gt;
&lt;li&gt;Audit your most-visited pages for accuracy before any move, starting with tutorials that call functions whose signatures changed recently.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why not fix accuracy during the migration?
&lt;/h2&gt;

&lt;p&gt;A migration touches every page, so cleaning up then feels efficient. But migration work competes with accuracy work: Redocly's &lt;a href="https://redocly.com/blog/documentation-migration-tips" rel="noopener noreferrer"&gt;tips for documentation migration&lt;/a&gt; include a rollback plan: "Rolling back early is better than rolling back late." The migration date may also never come: MkDocs 2.0 is undated, and Zensical is in alpha.&lt;/p&gt;

&lt;h2&gt;
  
  
  How EkLine plugs into a Material for MkDocs repo
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;On a Material for MkDocs site&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Where your content lives&lt;/td&gt;
&lt;td&gt;Markdown in &lt;code&gt;docs/&lt;/code&gt; next to &lt;code&gt;mkdocs.yml&lt;/code&gt;, as the &lt;a href="https://squidfunk.github.io/mkdocs-material/creating-your-site" rel="noopener noreferrer"&gt;Material setup guide&lt;/a&gt; lays out, in a GitHub or GitLab repository&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What EkLine watches&lt;/td&gt;
&lt;td&gt;Code repositories you select in the &lt;a href="https://docs.ekline.io/agent/github-app-setup/" rel="noopener noreferrer"&gt;GitHub App&lt;/a&gt;, listed apart from documentation repositories; &lt;a href="https://docs.ekline.io/changelog/" rel="noopener noreferrer"&gt;GitLab merge requests&lt;/a&gt; too&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;When it reads a change&lt;/td&gt;
&lt;td&gt;Docs Agent reads &lt;a href="https://docs.ekline.io/agent/automatic-pr-review/" rel="noopener noreferrer"&gt;each pull request as it opens&lt;/a&gt;; a weekly &lt;a href="https://docs.ekline.io/agent/prevent-documentation-drift/" rel="noopener noreferrer"&gt;Docs Drift Review&lt;/a&gt; audits the last 30 days of GitHub activity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where its pull request lands&lt;/td&gt;
&lt;td&gt;Your docs repository, as &lt;a href="https://docs.ekline.io/agent/update-review/" rel="noopener noreferrer"&gt;Markdown edits in a pull request&lt;/a&gt; under the configured documentation path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Which CI check runs&lt;/td&gt;
&lt;td&gt;Docs Reviewer, which checks each docs change for style, consistency, links, and accuracy, through the &lt;a href="https://docs.ekline.io/reviewer/quickstart/github-action/" rel="noopener noreferrer"&gt;GitHub Action&lt;/a&gt;. Its &lt;a href="https://docs.ekline.io/reviewer/configuration/framework-support/" rel="noopener noreferrer"&gt;framework values&lt;/a&gt; are mintlify, astro, fern, docusaurus, and gitbook, so on MkDocs it reviews your Markdown without framework-specific component handling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What stays in MkDocs&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;mkdocs.yml&lt;/code&gt;, the Material theme, your plugins, your build, and your hosting&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Follow the rename through it. The engineer opens the pull request in the code repository, and automatic PR review assesses it once, from "the PR diff, title, and description." A high-confidence result triggers a docs pull request; a medium one posts a suggestion comment and leaves the call to you. The fix to look for is the tutorial line that still passes the old argument; your team merges it, and your MkDocs build publishes it. If the reason for the rename lives in a Jira ticket, the PR review won't read it, so open a Docs Agent session and &lt;a href="https://docs.ekline.io/agent/combine-sources/" rel="noopener noreferrer"&gt;combine the pull request with the ticket&lt;/a&gt;. Docs Agent is available to all plans, but we grant access on request.&lt;/p&gt;

&lt;p&gt;Docs Agent works on Markdown in a GitHub or GitLab repository, so after a move, point the documentation path at the new content folder.&lt;/p&gt;

&lt;p&gt;Can an accuracy pass cover most of a site before you decide on a move? ContextQA, whose docs are hosted on GitBook rather than MkDocs, used EkLine over one quarter, April 7 to July 6, 2026, and &lt;a href="https://ekline.io/case-study/how-contextqas-engineers-merge-97-percent-of-what-an-ai-agent-writes" rel="noopener noreferrer"&gt;"brought about 80% of the documentation set, 90 of 113 pages, up to date."&lt;/a&gt; Finish a pass like that before you move, and the new site starts from true pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does EkLine replace your docs platform?
&lt;/h3&gt;

&lt;p&gt;No. MkDocs, the Material theme, &lt;code&gt;mkdocs.yml&lt;/code&gt;, your plugins, and your hosting stay where they are. EkLine reads pull requests in your code repositories, opens Markdown pull requests in your docs repository, and adds Docs Reviewer as a CI check on that Markdown.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does EkLine still work if we move off MkDocs later?
&lt;/h3&gt;

&lt;p&gt;It should, as long as the new site keeps its Markdown or MDX in a GitHub or GitLab repository, because Docs Agent works on repositories and pull requests. On an Astro and Starlight site such as Potluck Docs, Docs Reviewer's astro framework value applies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can't we script this ourselves?
&lt;/h3&gt;

&lt;p&gt;A script covers some ground. In the Ask HN thread, cyanydeez described "a for loop that runs nightly on any files updated the prior day," pointing local AI at the Markdown beside each changed file. On MkDocs, docstrings already play that role through mkdocstrings, while the tutorials that call a function sit elsewhere in the docs folder. Someone still checks every rewrite, the half rstagi flagged: "it's hard to fact-check everything all the time." EkLine drafts tutorial fixes as pull requests for review, and because they are plain Markdown, they carry over if you move.&lt;/p&gt;

&lt;p&gt;Bring one recent pull request that changed a Python signature. To see what EkLine would change in your tutorials, &lt;a href="https://ekline.io" rel="noopener noreferrer"&gt;book a demo with EkLine&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>documentation</category>
      <category>python</category>
      <category>opensource</category>
      <category>markdown</category>
    </item>
    <item>
      <title>How to keep ReadMe docs in sync with your code and OpenAPI spec</title>
      <dc:creator>Alisa Hester</dc:creator>
      <pubDate>Mon, 12 Oct 2026 06:31:02 +0000</pubDate>
      <link>https://dev.to/alisa_hester_1/how-to-keep-readme-docs-in-sync-with-your-code-and-openapi-spec-5aae</link>
      <guid>https://dev.to/alisa_hester_1/how-to-keep-readme-docs-in-sync-with-your-code-and-openapi-spec-5aae</guid>
      <description>&lt;p&gt;"How do I keep ReadMe docs in sync with my code and my OpenAPI spec?" ReadMe handles half of that job. When you re-sync an OpenAPI file, ReadMe &lt;a href="https://docs.readme.com/main/docs/openapi-resyncing" rel="noopener noreferrer"&gt;updates the spec-derived parts of each API page in place and leaves the Markdown you wrote alone&lt;/a&gt;. Your guides, recipes, and examples are that Markdown, so they need an update path of their own.&lt;/p&gt;

&lt;p&gt;Follow one change: your API adds a new required request parameter. You run &lt;code&gt;rdme openapi upload&lt;/code&gt;, and the reference page for that operation shows the parameter in an updated request panel. The getting-started guide and the recipe that call the same endpoint still show a request without it.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;When you re-sync the spec, ReadMe updates the reference pages and keeps your hand-written Markdown exactly as you left it.&lt;/li&gt;
&lt;li&gt;Use bi-directional sync or rdme for Markdown, never both, and keep every &lt;code&gt;operationId&lt;/code&gt; stable.&lt;/li&gt;
&lt;li&gt;Guides, recipes, and samples need a review step tied to code changes, which EkLine adds from GitHub or GitLab.&lt;/li&gt;
&lt;/ul&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%2Fjz0rsvhe4gq91jh31q7y.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%2Fjz0rsvhe4gq91jh31q7y.png" alt="Diagram: a ReadMe API page split in two; the top half from the OpenAPI spec (parameters, request panel) updates on every re-sync, the hand-written bottom half (guide, recipe example) is kept as is and is the half EkLine keeps current" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What ReadMe keeps current when you re-sync
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.readme.com/main/docs/bi-directional-sync" rel="noopener noreferrer"&gt;Bi-directional sync&lt;/a&gt; connects your project to "a GitHub, GitLab, or Bitbucket repository so content stays in step whether you edit in ReadMe or in your repo," and "All content types sync, including guides, API reference pages, custom pages, recipes, and changelogs." The repository "must be empty" before you connect it, with "no commits or files," so it cannot be your existing API codebase. Your code and your docs end up in separate repositories.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/readmeio/rdme" rel="noopener noreferrer"&gt;rdme&lt;/a&gt; is "ReadMe's official command-line interface (CLI) and GitHub Action," and it "&lt;a href="https://docs.readme.com/main/docs/rdme" rel="noopener noreferrer"&gt;pushes one way, from your files to ReadMe&lt;/a&gt;, so edits made in the ReadMe editor don't flow back to your repository."&lt;/p&gt;

&lt;p&gt;ReadMe matches pages on &lt;code&gt;operationId&lt;/code&gt; and "updates the existing API Reference pages in place rather than creating a second set." "The parts of the page that come from the document, such as the summary, parameters, and request and response panels, update to match the new definition." The new required parameter appears on the reference page with no edit from you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where hand-written ReadMe pages drift
&lt;/h2&gt;

&lt;p&gt;Test it on your own hub. Open the last merged pull request that added or changed a request parameter. Did anything flag the guide or recipe that calls that endpoint? Each native tool did its own job:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Native check&lt;/th&gt;
&lt;th&gt;What it verifies&lt;/th&gt;
&lt;th&gt;What it does not see&lt;/th&gt;
&lt;th&gt;Where EkLine fits&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://docs.readme.com/main/docs/openapi-resyncing" rel="noopener noreferrer"&gt;OpenAPI re-sync&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Spec-derived parts of each reference page, matched on &lt;code&gt;operationId&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Your hand-written Markdown, which it keeps as is&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://docs.ekline.io/agent/automatic-pr-review/" rel="noopener noreferrer"&gt;Automatic PR review&lt;/a&gt; assesses the API pull request and can start a guide edit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://docs.readme.com/main/docs/bi-directional-sync" rel="noopener noreferrer"&gt;Bi-directional sync&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;The repository and the hub hold the same pages&lt;/td&gt;
&lt;td&gt;Whether a recipe's request still matches the API&lt;/td&gt;
&lt;td&gt;Docs Agent opens its pull request in that &lt;a href="https://docs.ekline.io/agent/github-app-setup/" rel="noopener noreferrer"&gt;documentation repository&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://docs.readme.com/main/docs/rdme" rel="noopener noreferrer"&gt;rdme&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Files in Git reach ReadMe, one way&lt;/td&gt;
&lt;td&gt;Whether the files it pushes are still true&lt;/td&gt;
&lt;td&gt;Docs Reviewer checks the Markdown in CI, without a &lt;a href="https://docs.ekline.io/reviewer/configuration/framework-support/" rel="noopener noreferrer"&gt;ReadMe framework value&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The same re-sync page says your Markdown "is kept through every kind of re-sync." That protects your writing, and it also means no ReadMe process updates it. Three places still hold the old request:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The getting-started guide that walks a reader through a first call.&lt;/li&gt;
&lt;li&gt;The recipe for that endpoint.&lt;/li&gt;
&lt;li&gt;Any prose or sample you added to the reference page itself, now sitting beside refreshed panels.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Third-party sync glue brings failure modes of its own. In May 2026, JohnRDOrazio found his project's old &lt;code&gt;github-readme-sync&lt;/code&gt; action "no longer maintained" and filed "&lt;a href="https://github.com/BibleGet-I-O/endpoint/issues/100" rel="noopener noreferrer"&gt;Replace broken ReadMe.io sync workflow with self-hosted docs (OntoKit pattern)&lt;/a&gt;." davidt99 reported that "&lt;a href="https://github.com/readmeio/rdme/issues/1355" rel="noopener noreferrer"&gt;Docs upload format are incompatible with bi-directional sync&lt;/a&gt;"; ReadMe closed it as not fixable. An &lt;code&gt;operationId&lt;/code&gt; change is worse than a rename: to ReadMe, "a changed ID is a deleted endpoint plus a new one. The old page, its URL, and any edits made to it are removed."&lt;/p&gt;

&lt;h2&gt;
  
  
  Does the GitHub AI Writer close the gap?
&lt;/h2&gt;

&lt;p&gt;For some teams, yes. ReadMe's &lt;a href="https://docs.readme.com/main/docs/github-ai-writer" rel="noopener noreferrer"&gt;GitHub AI Writer&lt;/a&gt; "Proposes documentation updates when pull requests change your codebase." When a pull request opens in a connected repository, it "analyzes the diff, identifies new endpoints that need documentation or setup steps that have changed, and drafts the update on a review branch in ReadMe." It is "Available on Pro plans and above." If your API code is on GitHub and the pull request holds everything the guide needs, it covers the example change.&lt;/p&gt;

&lt;p&gt;Its docs describe only a GitHub integration, so GitLab code has no documented path. Its docs describe it reading the diff and describe no input from tickets, Slack threads, or specs. It starts when a pull request opens, so work that merged before you connected it waits for someone to notice.&lt;/p&gt;

&lt;p&gt;When is ReadMe's tooling enough on its own? When the guide edit can ride in the same pull request as the code and that pull request carries every fact, as with Markdown kept beside the code and pushed by rdme. Bi-directional sync needs an empty repository when you connect it, so in practice the guides start in a repository of their own. Once facts sit there, or in a ticket, a Slack thread, or a spec, the same-PR rule leans on memory; commenters in an &lt;a href="https://news.ycombinator.com/item?id=48825447" rel="noopener noreferrer"&gt;Ask HN thread&lt;/a&gt; on the topic rely on docs-first rules and nightly scripts.&lt;/p&gt;

&lt;h2&gt;
  
  
  A ReadMe sync checklist for this week
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Pick one path for Markdown pages. ReadMe says "If your project has Bi-Directional Sync enabled, you don't need rdme," though you still need the CLI for OpenAPI files.&lt;/li&gt;
&lt;li&gt;Check every synced branch's rules: "your Git provider can reject the commits ReadMe pushes to it" when protection rules or rulesets apply.&lt;/li&gt;
&lt;li&gt;Name branches exactly after your ReadMe versions, as ReadMe requires, so the branch a change merges into is the version it updates.&lt;/li&gt;
&lt;li&gt;Give every operation an explicit, stable &lt;code&gt;operationId&lt;/code&gt;, because a generated ID changes with the path and takes the old page with it.&lt;/li&gt;
&lt;li&gt;When the spec changes, search your guides, recipes, and reference-page prose for the changed operation before you close the release.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How EkLine plugs into a ReadMe setup
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part of the workflow&lt;/th&gt;
&lt;th&gt;Where it sits&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Your Markdown pages&lt;/td&gt;
&lt;td&gt;The GitHub or GitLab repository ReadMe syncs, or the repository your CI runs rdme from.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What EkLine watches&lt;/td&gt;
&lt;td&gt;Your API code. The &lt;a href="https://docs.ekline.io/agent/github-app-setup/" rel="noopener noreferrer"&gt;GitHub App&lt;/a&gt; separates Code Repositories from Documentation Repositories, and Docs Agent also reviews &lt;a href="https://docs.ekline.io/changelog/" rel="noopener noreferrer"&gt;GitLab merge requests&lt;/a&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where the Docs Agent pull request lands&lt;/td&gt;
&lt;td&gt;Your docs repository, on the branch for the ReadMe version it documents.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How the change reaches your hub&lt;/td&gt;
&lt;td&gt;ReadMe's sync, or your rdme step, after your team merges.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI check&lt;/td&gt;
&lt;td&gt;Docs Reviewer checks the Markdown for style, consistency, links, and accuracy. Its &lt;a href="https://docs.ekline.io/reviewer/configuration/framework-support/" rel="noopener noreferrer"&gt;framework support page&lt;/a&gt; lists no ReadMe value, so it works without ReadMe-specific component handling.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bitbucket&lt;/td&gt;
&lt;td&gt;Docs Reviewer only, as a &lt;a href="https://docs.ekline.io/reviewer/integrations/bitbucket-integration/" rel="noopener noreferrer"&gt;Bitbucket Pipelines&lt;/a&gt; check.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What stays in ReadMe&lt;/td&gt;
&lt;td&gt;Hosting, the editor, versions, and the reference generated from your spec.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;An engineer opens a pull request in the API repository that adds the required parameter. &lt;a href="https://docs.ekline.io/agent/automatic-pr-review/" rel="noopener noreferrer"&gt;Automatic PR review&lt;/a&gt; assesses its diff, title, and description once, when it opens. A high-confidence result starts a docs pull request editing existing pages such as the guide and the recipe; a medium result posts a suggestion comment. You merge it, ReadMe's sync or your rdme step carries the guide to the hub, and the reference updates from the spec. If the review stayed silent, the weekly &lt;a href="https://docs.ekline.io/agent/prevent-documentation-drift/" rel="noopener noreferrer"&gt;Docs Drift Review&lt;/a&gt; audits the last 30 days of GitHub activity. When the parameter came from a ticket or spec, a Docs Agent session can &lt;a href="https://docs.ekline.io/agent/combine-sources/" rel="noopener noreferrer"&gt;combine that source&lt;/a&gt; with the pull request. &lt;a href="https://docs.ekline.io/agent/scheduled-agents/" rel="noopener noreferrer"&gt;Code Sample Review&lt;/a&gt; "Verifies that code samples in your docs are accurate and runnable."&lt;/p&gt;

&lt;p&gt;Can one writer keep pace with a busy API? At &lt;a href="https://ekline.io/case-study/how-socure-built-developer-experience-into-a-moat-with-ekline" rel="noopener noreferrer"&gt;Socure&lt;/a&gt;, an identity intelligence company, one technical writer merged 348 documentation updates in seven weeks with no added headcount. The case study does not say which docs platform Socure uses.&lt;/p&gt;

&lt;p&gt;If you are weighing a self-hosted move, &lt;a href="https://potluck.ekline.io" rel="noopener noreferrer"&gt;Potluck Docs&lt;/a&gt;, EkLine's open-source Astro and Starlight template, renders OpenAPI references with Scalar and a built-in client for sending real requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does EkLine replace your docs platform?
&lt;/h3&gt;

&lt;p&gt;No. ReadMe keeps hosting your hub, generating the reference from your spec, and running sync or rdme. EkLine covers the pages ReadMe leaves to you: it reads code changes as pull requests open, sweeps merged work on a schedule, and opens pull requests against your guides and recipes for review. For a side-by-side view, read &lt;a href="https://ekline.io/blog/ekline-vs-readme" rel="noopener noreferrer"&gt;EkLine vs ReadMe&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I use rdme or bi-directional sync?
&lt;/h3&gt;

&lt;p&gt;Use one for Markdown pages. Bi-directional sync fits a team that edits in both the ReadMe editor and Git, and it needs an empty repository, branch names that match versions, and branch rules that let ReadMe push. rdme fits a team that treats Git as the only source, because it pushes one way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does EkLine work if ReadMe syncs to Bitbucket?
&lt;/h3&gt;

&lt;p&gt;Partly. Docs Agent works with GitHub and GitLab, so it does not open pull requests in a Bitbucket repository. Docs Reviewer runs as a Bitbucket Pipelines check on your Markdown.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can't we script this ourselves?
&lt;/h3&gt;

&lt;p&gt;Some teams do. In the Ask HN thread, cyanydeez described "a for loop that runs nightly on any files updated the prior day," handing the Markdown file next to each changed file to local AI to revise. With bi-directional sync, guides start in their own repository, so a recipe has no file beside the API code to pair with. Each rewrite also needs checking, which rstagi called hard: "it's hard to fact-check everything all the time." EkLine starts from the API pull request and drafts guide edits as a pull request in the repository ReadMe syncs, where a person approves them.&lt;/p&gt;

&lt;p&gt;Take the last pull request that added a parameter to your API, and bring it to the call. To see what Docs Agent would change in your ReadMe guides after your next spec update, &lt;a href="https://ekline.io" rel="noopener noreferrer"&gt;book a demo&lt;/a&gt;. Docs Agent is available to all plans, with access granted on request.&lt;/p&gt;

</description>
      <category>documentation</category>
      <category>api</category>
      <category>restapi</category>
      <category>github</category>
    </item>
    <item>
      <title>How to keep Mintlify docs up to date when code changes</title>
      <dc:creator>Alisa Hester</dc:creator>
      <pubDate>Mon, 12 Oct 2026 06:16:01 +0000</pubDate>
      <link>https://dev.to/alisa_hester_1/how-to-keep-mintlify-docs-up-to-date-when-code-changes-40bf</link>
      <guid>https://dev.to/alisa_hester_1/how-to-keep-mintlify-docs-up-to-date-when-code-changes-40bf</guid>
      <description>&lt;p&gt;Mintlify already automates the structural half of keeping docs current: it builds API reference pages from your OpenAPI spec, checks links, validates builds, syncs with GitHub, and can draft pull requests when code merges. The half that slips is the prose and the examples whose facts live in other repositories, tickets, and threads. That half needs a review step, and it is where an EkLine pull request fits.&lt;/p&gt;

&lt;p&gt;Follow one change through this post: an endpoint's auth header changes. The reference page generated from your spec updates. The quickstart and a guide still show the old header, and &lt;code&gt;mint broken-links&lt;/code&gt; passes, because every link on those pages still resolves.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Mintlify regenerates API reference pages from your OpenAPI spec; a spec hosted at a URL needs a Trigger deployment call.&lt;/li&gt;
&lt;li&gt;The Mintlify CLI checks links and build warnings, so an outdated header in valid MDX passes.&lt;/li&gt;
&lt;li&gt;Mintlify automations draft pull requests after code merges, and Mintlify's own guidance still keeps review and sign-offs.&lt;/li&gt;
&lt;li&gt;EkLine reads each code pull request as it opens and lands its docs pull request in the repository Mintlify deploys from.&lt;/li&gt;
&lt;/ul&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%2Fb32tqim9exn6jbv4449d.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%2Fb32tqim9exn6jbv4449d.png" alt="Diagram: after one API change, the Mintlify API reference page generated from OpenAPI updates itself and link checks pass, while the quickstart and auth guide still show the old header; EkLine drafts the fix as a pull request and Mintlify deploys on merge" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Mintlify already keeps current
&lt;/h2&gt;

&lt;p&gt;Generated API pages. Mintlify's &lt;a href="https://www.mintlify.com/docs/api-playground/openapi-setup" rel="noopener noreferrer"&gt;OpenAPI setup&lt;/a&gt; lets you "Reference any number of OpenAPI specifications in the navigation element of your &lt;code&gt;docs.json&lt;/code&gt; to create pages for your API endpoints." When the spec lives in your repository, a spec change is a Git push and the site redeploys. When you host the spec at a URL, Mintlify warns that "changes to the spec don't trigger a Git push, so your docs won't redeploy automatically," and tells you to call the Trigger deployment API endpoint in the same CI action that generates or updates the spec.&lt;/p&gt;

&lt;p&gt;Link and build checks. In the &lt;a href="https://www.mintlify.com/docs/cli/commands" rel="noopener noreferrer"&gt;Mintlify CLI&lt;/a&gt;, &lt;code&gt;mint broken-links&lt;/code&gt; checks internal links; &lt;code&gt;--check-anchors&lt;/code&gt; also validates anchor links against heading slugs, and &lt;code&gt;--check-external&lt;/code&gt; also checks external URLs. &lt;code&gt;mint validate&lt;/code&gt; "Validate[s] your documentation build in strict mode. Exits with an error if there are any warnings or errors."&lt;/p&gt;

&lt;p&gt;Git sync. Mintlify "uses a GitHub App to automatically sync your documentation with your GitHub repository" and &lt;a href="https://www.mintlify.com/docs/deploy/github" rel="noopener noreferrer"&gt;deploys when you push&lt;/a&gt;. In a monorepo, you can "specify a subdirectory if your docs are not at the repository root."&lt;/p&gt;

&lt;p&gt;Drafted updates. Mintlify &lt;a href="https://www.mintlify.com/docs/agent/suggestions" rel="noopener noreferrer"&gt;automations&lt;/a&gt; include a code change trigger that "Runs when a pull request merges in one or more connected source code repositories." The agent "reads your project content and any connected repositories, then follows the prompt to make updates." Automations require a Pro or Enterprise plan, "Each run that updates your docs costs 250 credits," and each automation can run up to 500 times per day. If the auth header change merges in a connected repository, this feature may already cover it, so check it before you add another tool.&lt;/p&gt;

&lt;p&gt;Mintlify still keeps people in the loop. Its &lt;a href="https://www.mintlify.com/docs/guides/maintenance" rel="noopener noreferrer"&gt;maintenance guide&lt;/a&gt; says "Update docs in the same pull request as the code change" and "Require a docs sign-off before closing any ticket that changes user-facing behavior," then sets monthly, quarterly, and annual reviews. A June 18, 2026 Mintlify article on &lt;a href="https://www.mintlify.com/library/how-to-stop-documentation-drift" rel="noopener noreferrer"&gt;stopping documentation drift&lt;/a&gt; adds that "pull request review is the safer setup for user-facing documentation."&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the auth header change still slips through
&lt;/h2&gt;

&lt;p&gt;Test your own repository. Open the last merged pull request that changed how your API authenticates or what a request must send. Did anything flag the quickstart or guide that shows the old request? If nothing did, the reason is in what each Mintlify check reads.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Native check&lt;/th&gt;
&lt;th&gt;What it verifies&lt;/th&gt;
&lt;th&gt;What it does not see&lt;/th&gt;
&lt;th&gt;Where EkLine fits&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.mintlify.com/docs/cli/commands" rel="noopener noreferrer"&gt;&lt;code&gt;mint broken-links&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Internal links, plus anchors and external URLs with flags&lt;/td&gt;
&lt;td&gt;A working link on a page whose sample sends the old header&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://docs.ekline.io/agent/automatic-pr-review/" rel="noopener noreferrer"&gt;Automatic PR review&lt;/a&gt; reads the API pull request and can start the docs fix&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.mintlify.com/docs/cli/commands" rel="noopener noreferrer"&gt;&lt;code&gt;mint validate&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;The build in strict mode, failing on any warning or error&lt;/td&gt;
&lt;td&gt;Valid MDX that describes old behavior&lt;/td&gt;
&lt;td&gt;Docs Reviewer comments on the docs change, with &lt;a href="https://docs.ekline.io/reviewer/configuration/framework-support/" rel="noopener noreferrer"&gt;Mintlify component support&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.mintlify.com/docs/api-playground/openapi-setup" rel="noopener noreferrer"&gt;OpenAPI pages&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Reference pages generated from your spec&lt;/td&gt;
&lt;td&gt;Quickstarts and guides written by hand in MDX&lt;/td&gt;
&lt;td&gt;Docs Agent edits those pages in your &lt;a href="https://docs.ekline.io/agent/github-app-setup/" rel="noopener noreferrer"&gt;documentation repository&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two gaps sit outside every check: a migration note that lives in a ticket or a Slack thread, and hand-kept files. In a Mintlify-based repository, drewdrewthis &lt;a href="https://github.com/langwatch/tasks/issues/688" rel="noopener noreferrer"&gt;reported on August 21, 2026&lt;/a&gt; that llms-full.txt "is maintained by hand, which means it drifts silently and there is no signal when it does."&lt;/p&gt;

&lt;p&gt;So when are native tools enough? When your API code and docs share a repository and every fact the guide needs is in the pull request, Mintlify's checks plus the same-PR rule from its &lt;a href="https://www.mintlify.com/docs/guides/maintenance" rel="noopener noreferrer"&gt;maintenance guide&lt;/a&gt; cover the change. When the facts live in tickets, Slack, specs, or a separate code repository, the rule depends on memory; practitioners in an &lt;a href="https://news.ycombinator.com/item?id=48825447" rel="noopener noreferrer"&gt;Ask HN thread&lt;/a&gt; on keeping docs current describe docs-first habits and homemade nightly scripts.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Mintlify upkeep checklist for this week
&lt;/h2&gt;

&lt;p&gt;You need write access to the docs repository and its CI. None of these steps needs EkLine.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If your OpenAPI spec is hosted at a URL, call the Trigger deployment endpoint in the CI action that updates the spec.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;mint broken-links&lt;/code&gt; with &lt;code&gt;--check-anchors&lt;/code&gt; and &lt;code&gt;--check-external&lt;/code&gt; before you publish; renamed headings change their slugs.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;mint validate&lt;/code&gt; to CI so a warning fails the build before readers see it.&lt;/li&gt;
&lt;li&gt;When a header, parameter, or endpoint name changes, search the whole docs repository for the old string, including hand-maintained files such as llms-full.txt. The link checks pass on this failure.&lt;/li&gt;
&lt;li&gt;Adopt the two process rules from Mintlify's maintenance guide: docs in the same pull request as the code, and a docs sign-off before closing any ticket that changes user-facing behavior.&lt;/li&gt;
&lt;li&gt;If you run automations, keep their pull requests on review. Mintlify's drift article calls review the safer setup.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How EkLine plugs into a Mintlify repository
&lt;/h2&gt;

&lt;p&gt;EkLine works in the repository Mintlify already deploys from, so your publishing path stays as it is.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;On Mintlify&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Where your content lives&lt;/td&gt;
&lt;td&gt;The repository Mintlify's GitHub App deploys from, at the root or in a monorepo subdirectory.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What EkLine watches&lt;/td&gt;
&lt;td&gt;The code repositories you select in the &lt;a href="https://docs.ekline.io/agent/github-app-setup/" rel="noopener noreferrer"&gt;EkLine GitHub App&lt;/a&gt;, which lists them apart from your documentation repository. Docs Agent also &lt;a href="https://docs.ekline.io/changelog/" rel="noopener noreferrer"&gt;reviews GitLab merge requests&lt;/a&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where its pull request lands&lt;/td&gt;
&lt;td&gt;Your docs repository. Mintlify deploys on merge, as it does today.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Which CI check runs&lt;/td&gt;
&lt;td&gt;Docs Reviewer, which checks each docs change for style, consistency, links, and accuracy, with the &lt;code&gt;mintlify&lt;/code&gt; framework value, set in its &lt;a href="https://docs.ekline.io/reviewer/integrations/cli-integration/" rel="noopener noreferrer"&gt;configuration or with &lt;code&gt;--framework&lt;/code&gt;&lt;/a&gt;. The &lt;a href="https://docs.ekline.io/reviewer/configuration/framework-support/" rel="noopener noreferrer"&gt;framework support page&lt;/a&gt; says "Mintlify currently has the most comprehensive component support."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What stays in Mintlify&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;docs.json&lt;/code&gt;, generated API pages, hosting, deploys, and any automations you run.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Walk the auth header change through Docs Agent. When the engineer opens the API pull request, or marks a draft ready, &lt;a href="https://docs.ekline.io/agent/automatic-pr-review/" rel="noopener noreferrer"&gt;automatic PR review&lt;/a&gt; reads the diff, title, and description once and scores the documentation impact. A low score stays silent, a medium score leaves a suggestion comment, and a high score starts a docs pull request in your Mintlify repository for the pages that need the new header. You review and merge it, and Mintlify deploys. Automatic PR review reads the pull request alone and leaves linked tickets out. When the migration note sits in a Jira ticket or a Slack thread, open a Docs Agent session and &lt;a href="https://docs.ekline.io/agent/combine-sources/" rel="noopener noreferrer"&gt;combine the sources&lt;/a&gt;; if they disagree, "the agent flags the conflict in the chat so you can decide." A weekly &lt;a href="https://docs.ekline.io/agent/prevent-documentation-drift/" rel="noopener noreferrer"&gt;Docs Drift Review&lt;/a&gt; sweeps what merged before you connected EkLine or scored below the threshold, and the &lt;a href="https://docs.ekline.io/agent/scheduled-agents/" rel="noopener noreferrer"&gt;Code Sample Review&lt;/a&gt; agent in Scheduled Agents "Verifies that code samples in your docs are accurate and runnable."&lt;/p&gt;

&lt;p&gt;If your doubt is whether reviewers will trust drafts they did not write: over one quarter, April 7 to July 6, 2026, &lt;a href="https://ekline.io/case-study/how-contextqas-engineers-merge-97-percent-of-what-an-ai-agent-writes" rel="noopener noreferrer"&gt;ContextQA's engineers&lt;/a&gt; "merged 97.5% of what the agent proposed, 79 updates, and set aside only two." ContextQA hosts its docs on GitBook, so read this as an EkLine result on another platform.&lt;/p&gt;

&lt;p&gt;The limits: the assessment runs once per pull request, you cannot customize its confidence thresholds, and Docs Agent works with GitHub and GitLab. Docs Agent is available to all plans, and EkLine grants access on request.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does EkLine replace your docs platform?
&lt;/h3&gt;

&lt;p&gt;No. Mintlify keeps your &lt;code&gt;docs.json&lt;/code&gt;, generated API reference, hosting, and deploys. EkLine adds a step before them: it reads code pull requests as they open, drafts docs pull requests into the repository Mintlify deploys from, and runs a Docs Reviewer check that handles Mintlify components. For a feature-by-feature view, read &lt;a href="https://ekline.io/blog/ekline-vs-mintlify-which-documentation-tool-is-right-for-you" rel="noopener noreferrer"&gt;EkLine vs Mintlify&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do Mintlify automations replace a docs review step?
&lt;/h3&gt;

&lt;p&gt;Mintlify's own guidance keeps the review step. Its drift article says Mintlify "can be configured to automerge workflow pull requests, but pull request review is the safer setup for user-facing documentation," and its maintenance guide still asks for docs sign-offs and scheduled reviews. An automation drafts the change; a reviewer decides whether it is right before it goes live.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can't we script this ourselves?
&lt;/h3&gt;

&lt;p&gt;You can, and the Ask HN thread shows one version: cyanydeez described "a for loop that runs nightly on any files updated the prior day," with local AI revising the Markdown file next to each one. On Mintlify, when the quickstart lives in a separate docs repository, nothing sits beside the changed API file to revise. Someone also has to check each nightly rewrite, and rstagi named that cost in the same thread: "it's hard to fact-check everything all the time." EkLine reads the API pull request and, at high confidence, opens a docs pull request for the pages that need the change, so the check happens in review.&lt;/p&gt;

&lt;p&gt;Bring one recent API pull request, such as the last auth change, and see what EkLine would have changed in your quickstart. &lt;a href="https://ekline.io" rel="noopener noreferrer"&gt;Book an EkLine demo&lt;/a&gt; to walk through your own Mintlify setup.&lt;/p&gt;

</description>
      <category>documentation</category>
      <category>api</category>
      <category>automation</category>
      <category>github</category>
    </item>
    <item>
      <title>How to keep GitBook in sync with GitHub, and accurate</title>
      <dc:creator>Alisa Hester</dc:creator>
      <pubDate>Mon, 12 Oct 2026 06:01:01 +0000</pubDate>
      <link>https://dev.to/alisa_hester_1/how-to-keep-gitbook-in-sync-with-github-and-accurate-2lpe</link>
      <guid>https://dev.to/alisa_hester_1/how-to-keep-gitbook-in-sync-with-github-and-accurate-2lpe</guid>
      <description>&lt;p&gt;To keep GitBook in sync with GitHub without losing edits, and keep the content accurate, turn on Git Sync, choose the first sync direction on purpose, and edit each page in one place while a change is open. That keeps the editor and your repository identical. For accuracy, make the synced repository the place where every correction lands, whether a writer, an engineer, or EkLine makes it.&lt;/p&gt;

&lt;p&gt;Take a common collision. An engineer renames a CLI flag in the code repository while a writer has an open change request in the GitBook editor on the page that documents that flag. The path below lands both edits without either one overwriting the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Git Sync already handles
&lt;/h2&gt;

&lt;p&gt;GitBook describes &lt;a href="https://gitbook.com/docs/docs-as-code/git-sync" rel="noopener noreferrer"&gt;Git Sync&lt;/a&gt; as bi-directional: "changes you make directly in GitBook's editor are automatically synced, as are any commits made on GitHub or GitLab." Your pages sit in the repository as Markdown, and GitBook adds safeguards around the sync:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When you &lt;a href="https://gitbook.com/docs/docs-as-code/git-sync/enabling-github-sync" rel="noopener noreferrer"&gt;enable GitHub Sync&lt;/a&gt;, you "Choose the source of truth for the initial sync," and GitBook warns that "GitHub → GitBook can replace your section's content," for example with an empty repository's empty content.&lt;/li&gt;
&lt;li&gt;Edits merged in GitBook reach Git as &lt;a href="https://gitbook.com/docs/docs-as-code/git-sync/commits" rel="noopener noreferrer"&gt;commits named&lt;/a&gt; &lt;code&gt;GITBOOK-{change_request_number}: {change_request_subject}&lt;/code&gt;, so your history shows which changes came from the editor.&lt;/li&gt;
&lt;li&gt;If your docs live in the code repository, &lt;a href="https://gitbook.com/docs/docs-as-code/git-sync/monorepos" rel="noopener noreferrer"&gt;site-wide Git Sync&lt;/a&gt; can "sync multiple spaces from one repository and branch," each space in its own directory.&lt;/li&gt;
&lt;li&gt;A change request that conflicts with the space's live content is &lt;a href="https://gitbook.com/docs/guides/editing-and-publishing-documentation/how-to-handle-merge-conflicts-in-gitbook" rel="noopener noreferrer"&gt;resolved&lt;/a&gt; with &lt;strong&gt;Keep primary&lt;/strong&gt; or &lt;strong&gt;Keep changed&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;You can &lt;a href="https://gitbook.com/docs/create-content/openapi/add-an-openapi-specification" rel="noopener noreferrer"&gt;add an OpenAPI spec&lt;/a&gt; by file upload, URL, or CLI. For a URL, "GitBook checks for updates automatically every 6 hours."&lt;/li&gt;
&lt;/ul&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%2F5yt4f4d0ypdjsaj9h13r.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%2F5yt4f4d0ypdjsaj9h13r.png" alt="Diagram: the GitBook editor and a GitHub or GitLab repo linked by two-way Git Sync, with a reviewed and merged EkLine pull request entering on the repo side; note to edit a page in one place at a time" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the flag rename still slips through
&lt;/h2&gt;

&lt;p&gt;Try this on your own space. Open the last merged pull request that renamed a flag, option, or command. Did anything flag the GitBook page that documents it? Each safeguard checks something real, and none of their docs describes comparing a page with the code.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Native check&lt;/th&gt;
&lt;th&gt;What it verifies&lt;/th&gt;
&lt;th&gt;What it does not see&lt;/th&gt;
&lt;th&gt;Where EkLine fits&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://gitbook.com/docs/docs-as-code/git-sync" rel="noopener noreferrer"&gt;Git Sync&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;The editor and the repository hold the same content&lt;/td&gt;
&lt;td&gt;Whether that content still matches the code&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://docs.ekline.io/agent/automatic-pr-review/" rel="noopener noreferrer"&gt;Automatic PR review&lt;/a&gt; reads the code pull request behind the rename&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://gitbook.com/docs/guides/editing-and-publishing-documentation/how-to-handle-merge-conflicts-in-gitbook" rel="noopener noreferrer"&gt;Keep primary or Keep changed&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Which side of a conflicting change request wins&lt;/td&gt;
&lt;td&gt;A pull request on the same page, which GitBook tracks separately&lt;/td&gt;
&lt;td&gt;The &lt;a href="https://docs.ekline.io/agent/update-review/" rel="noopener noreferrer"&gt;docs pull request&lt;/a&gt; lands in the synced repository for review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://gitbook.com/docs/create-content/openapi/add-an-openapi-specification" rel="noopener noreferrer"&gt;OpenAPI by URL&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;The reference, checked every 6 hours&lt;/td&gt;
&lt;td&gt;The guides written around it&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://docs.ekline.io/agent/scheduled-agents/" rel="noopener noreferrer"&gt;Code Sample Review&lt;/a&gt; checks the samples in those guides&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://gitbook.com/docs/gitbook-agent/overview" rel="noopener noreferrer"&gt;GitBook Agent&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Change requests, as a linter, and support signals&lt;/td&gt;
&lt;td&gt;Code diffs, which its docs do not describe reading&lt;/td&gt;
&lt;td&gt;EkLine starts from the code change&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;GitBook's &lt;a href="https://gitbook.com/docs/docs-as-code/git-sync/troubleshooting" rel="noopener noreferrer"&gt;Git Sync troubleshooting page&lt;/a&gt; is clear that the two sides track work separately: "Creating a pull request in GitHub or GitLab doesn't create a change request in GitBook, and creating a change request in GitBook doesn't create a pull request in your repository." The writer's change request and a pull request on the same page proceed without seeing each other.&lt;/p&gt;

&lt;p&gt;Existing repositories add two traps. The same page says GitBook "may create new markdown files instead of using the existing ones," and that creating readme files through the GitBook UI "Creates duplicate README files in your repository." In a June 2025 &lt;a href="https://github.com/GitbookIO/gitbook/issues/3333" rel="noopener noreferrer"&gt;issue filed against GitBook&lt;/a&gt;, marmarmo listed "Loss of documentation with overwrites," called it "a common pitfall in GitHub-synced projects," and reported that "the GitBook space had to be hard reset to remove duplicated instances."&lt;/p&gt;

&lt;p&gt;Accuracy is a separate job: Git Sync mirrors the old flag name to both sides. On &lt;a href="https://news.ycombinator.com/item?id=44026945" rel="noopener noreferrer"&gt;Hacker News&lt;/a&gt;, themast wrote that "writing an OpenAPI spec and then keeping a separate GitBook in sync felt like twice the work."&lt;/p&gt;

&lt;p&gt;GitBook Agent covers part of this. &lt;a href="https://gitbook.com/docs/gitbook-agent/overview" rel="noopener noreferrer"&gt;GitBook's documentation&lt;/a&gt; says it writes docs from a prompt, can "Review change requests" as a docs linter, and can use "support conversations, tickets, and threads from your connected tools" to propose updates. It is free for all plans in beta, with 10 free messages a week outside Pro and trials. For a fix that starts in support, it works inside the editor your writers already use. Its &lt;a href="https://www.gitbook.com/blog/introducing-docs-agent" rel="noopener noreferrer"&gt;December 10, 2025 launch post&lt;/a&gt; said that "Soon, GitBook Agent will be able to connect to" support conversations, Slack threads, GitHub issues, and more, and it describes no reading of pull requests or code diffs, where the flag rename starts.&lt;/p&gt;

&lt;p&gt;Is a native setup enough? Yes, if your space syncs from the code repository itself, which site-wide Git Sync allows, and the pull request holds every fact the page needs: a rule that the rename and its doc edit merge together covers you. No, when the reason for the rename sits in a ticket, a Slack thread, a spec, or a separate code repository. In an &lt;a href="https://news.ycombinator.com/item?id=48825447" rel="noopener noreferrer"&gt;Ask HN thread&lt;/a&gt; about keeping documentation up to date, commenters trade docs-first rules and nightly scripts.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Git Sync safety checklist for this week
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Choose the first sync direction on purpose, and confirm the repository holds the content you want before you pick GitHub → GitBook.&lt;/li&gt;
&lt;li&gt;Create &lt;code&gt;README.md&lt;/code&gt; in the repository and avoid creating readme files in the GitBook UI.&lt;/li&gt;
&lt;li&gt;Give each page one place of editing during a change: when a pull request touches a page with an open change request, finish one before you merge the other.&lt;/li&gt;
&lt;li&gt;Add your OpenAPI spec by URL so the reference refreshes on the 6-hour check, and click &lt;strong&gt;Check for updates&lt;/strong&gt; after a release.&lt;/li&gt;
&lt;li&gt;Know the recovery path: if Git Sync shows an unexpected error, GitBook says merging a small change request exports all content again, and a small commit imports it again.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where EkLine fits in a Git-synced space
&lt;/h2&gt;

&lt;p&gt;EkLine works in the repository, so Git Sync has to be on; a space edited only in GitBook has no repository to receive a pull request.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;With GitBook&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Where your content lives&lt;/td&gt;
&lt;td&gt;Your GitBook space, synced to a GitHub or GitLab repository. Docs Agent works with GitHub and GitLab, not Bitbucket.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What EkLine watches&lt;/td&gt;
&lt;td&gt;Code repositories, which the &lt;a href="https://docs.ekline.io/agent/github-app-setup/" rel="noopener noreferrer"&gt;EkLine GitHub App&lt;/a&gt; lists apart from documentation repositories.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;When it reads a change&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://docs.ekline.io/agent/automatic-pr-review/" rel="noopener noreferrer"&gt;Automatic PR review&lt;/a&gt; assesses each pull request once, when it opens or moves from draft to ready. A weekly &lt;a href="https://docs.ekline.io/agent/prevent-documentation-drift/" rel="noopener noreferrer"&gt;Docs Drift Review&lt;/a&gt; sweeps what merged before EkLine was connected or fell below the threshold.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where its pull request lands&lt;/td&gt;
&lt;td&gt;The Git Sync repository, as a &lt;a href="https://docs.ekline.io/agent/update-review/" rel="noopener noreferrer"&gt;docs pull request&lt;/a&gt; your team reviews.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Which CI check runs&lt;/td&gt;
&lt;td&gt;Docs Reviewer, which checks each docs change for style, consistency, links, and accuracy, through its &lt;a href="https://docs.ekline.io/reviewer/quickstart/github-action/" rel="noopener noreferrer"&gt;GitHub Action&lt;/a&gt;; &lt;code&gt;gitbook&lt;/code&gt; is one of its five &lt;a href="https://docs.ekline.io/reviewer/configuration/framework-support/" rel="noopener noreferrer"&gt;framework values&lt;/a&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What stays in GitBook&lt;/td&gt;
&lt;td&gt;The editor, change requests, hosting, GitBook Agent, and Git Sync.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Back to the flag rename. When the engineer opens the pull request, Docs Agent reads its diff, title, and description. A high-confidence result opens a docs pull request in the synced repository that updates the affected page; a medium one leaves a suggestion comment. The writer merges or closes the open change request on that page first, then reviews the docs pull request and merges it through the normal branch, and Git Sync, GitBook's own feature, carries it into the editor. The review reads the pull request alone ("Linked Jira or Linear tickets are not included"), so a rename that starts in a ticket goes through a Docs Agent session that can &lt;a href="https://docs.ekline.io/agent/combine-sources/" rel="noopener noreferrer"&gt;combine sources&lt;/a&gt;. For a feature-by-feature view, read &lt;a href="https://ekline.io/blog/ekline-vs-gitbook" rel="noopener noreferrer"&gt;EkLine vs GitBook&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Will your engineers go along with it? P0 Security, a GitBook team, kept "Same GitHub. Same PR workflow. Same GitBook hosting. Same review process." Its &lt;a href="https://ekline.io/case-study/how-p0-securitys-engineering-team-stopped-writing-docs-and-started-shipping-faster" rel="noopener noreferrer"&gt;case study&lt;/a&gt; reports its engineers reclaimed 150+ hours in 6 months.&lt;/p&gt;

&lt;p&gt;Will it reach pages that are already stale? ContextQA, also on GitBook, brought 90 of 113 pages up to date in one quarter; the &lt;a href="https://ekline.io/case-study/how-contextqas-engineers-merge-97-percent-of-what-an-ai-agent-writes" rel="noopener noreferrer"&gt;ContextQA case study&lt;/a&gt; adds, "The pages doing that answering are the ones EkLine refreshed."&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does EkLine replace your docs platform?
&lt;/h3&gt;

&lt;p&gt;No. GitBook keeps the editor, change requests, hosting, and Git Sync. EkLine adds docs pull requests in your synced GitHub or GitLab repository when code changes, plus a Docs Reviewer check with GitBook framework support. Writers keep working in GitBook and see each merged update there.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which side wins when the GitBook editor and the repo both change?
&lt;/h3&gt;

&lt;p&gt;GitBook's Git Sync overview and troubleshooting pages do not say. They state that pull requests and change requests are separate, and inside GitBook a conflicting change request is settled with Keep primary or Keep changed. Avoid the race: edit a page in one place at a time, and close or merge the change request before merging a pull request on the same page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need a GitBook alternative to keep API docs current?
&lt;/h3&gt;

&lt;p&gt;Not for the reference, since GitBook checks a spec added by URL every 6 hours. The guides around it need the repository workflow above. If you decide to leave GitBook for a self-hosted, Git-native site, &lt;a href="https://potluck.ekline.io" rel="noopener noreferrer"&gt;Potluck Docs&lt;/a&gt; is EkLine's open source (MIT) Astro + Starlight template.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can't we script this ourselves?
&lt;/h3&gt;

&lt;p&gt;Partly. In the Ask HN thread, cyanydeez described "a for loop that runs nightly on any files updated the prior day," with local AI revising the Markdown file beside each changed file. When your space syncs from a separate docs repository, the page that documents a flag does not sit beside the code that renamed it. And every nightly rewrite still needs a reader, the cost rstagi named: "it's hard to fact-check everything all the time." EkLine reads the code pull request instead, and its reviewed pull request reaches the editor through Git Sync.&lt;/p&gt;

&lt;p&gt;Pick one merged pull request that renamed something your GitBook pages mention, and bring it along. To see what EkLine would change in your GitBook repository, &lt;a href="https://ekline.io" rel="noopener noreferrer"&gt;book a demo&lt;/a&gt;. Docs Agent is available to all plans, and access is granted on request.&lt;/p&gt;

</description>
      <category>documentation</category>
      <category>github</category>
      <category>git</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
