<?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: wolfejam.dev</title>
    <description>The latest articles on DEV Community by wolfejam.dev (@wolfejam).</description>
    <link>https://dev.to/wolfejam</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%2F3591934%2F481fd96a-6927-40bf-8e0d-83eb940f0d40.jpeg</url>
      <title>DEV Community: wolfejam.dev</title>
      <link>https://dev.to/wolfejam</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wolfejam"/>
    <language>en</language>
    <item>
      <title>FastMCP 3 4 migration: the breaking changes that compile</title>
      <dc:creator>wolfejam.dev</dc:creator>
      <pubDate>Mon, 07 Sep 2026 20:31:26 +0000</pubDate>
      <link>https://dev.to/wolfejam/fastmcp-3-4-migration-the-breaking-changes-that-compile-k6p</link>
      <guid>https://dev.to/wolfejam/fastmcp-3-4-migration-the-breaking-changes-that-compile-k6p</guid>
      <description>&lt;p&gt;&lt;em&gt;FastMCP 4 is GA. If you have an MCP server or client on &lt;code&gt;fastmcp&lt;/code&gt; 3.x, you'll&lt;br&gt;
upgrade soon. Most of it is painless — &lt;code&gt;FastMCP(...)&lt;/code&gt;, &lt;code&gt;@mcp.tool&lt;/code&gt;, and&lt;br&gt;
&lt;code&gt;mcp.run(transport=...)&lt;/code&gt; are all unchanged. The parts that aren't painless are the&lt;br&gt;
parts that don't announce themselves.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;These are field notes on top of the official&lt;br&gt;
&lt;a href="https://gofastmcp.com/getting-started/upgrading/from-fastmcp-3" rel="noopener noreferrer"&gt;Upgrading from FastMCP 3&lt;/a&gt;&lt;br&gt;
guide — the items that bit hardest when I moved one MCP server and two clients,&lt;br&gt;
in the order they bit.&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  1. &lt;code&gt;pip install -U fastmcp&lt;/code&gt; can leave you half-broken
&lt;/h2&gt;

&lt;p&gt;FastMCP 4 is split into extras. The &lt;code&gt;fastmcp&lt;/code&gt; package is now a thin meta-package&lt;br&gt;
that depends on &lt;code&gt;fastmcp-slim[client,server]&lt;/code&gt;; &lt;code&gt;fastmcp-slim&lt;/code&gt; carries the actual&lt;br&gt;
code, and its extras are &lt;code&gt;client&lt;/code&gt;, &lt;code&gt;server&lt;/code&gt;, &lt;code&gt;mcp&lt;/code&gt;, &lt;code&gt;anthropic&lt;/code&gt;, &lt;code&gt;apps&lt;/code&gt;, &lt;code&gt;azure&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;code-mode&lt;/code&gt;, &lt;code&gt;gemini&lt;/code&gt;, &lt;code&gt;openai&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;On a &lt;strong&gt;fresh&lt;/strong&gt; install this is invisible — &lt;code&gt;pip install fastmcp&lt;/code&gt; pulls&lt;br&gt;
&lt;code&gt;fastmcp-slim[client,server]&lt;/code&gt; and everything works.&lt;/p&gt;

&lt;p&gt;I upgraded &lt;strong&gt;in place&lt;/strong&gt; with &lt;code&gt;pip install -U fastmcp&lt;/code&gt; over &lt;code&gt;fastmcp 3.2.x&lt;/code&gt;, and pip&lt;br&gt;
did not re-resolve those base extras. Result: an importable shell with nothing in&lt;br&gt;
it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;fastmcp&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;fastmcp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fastmcp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastmcp&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;
&lt;span class="nb"&gt;ImportError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cannot&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Client&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fastmcp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unknown&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks exactly like a broken release. It isn't — it's the 4.x extras split not&lt;br&gt;
getting re-resolved on an in-place upgrade. (FastMCP separately documents a&lt;br&gt;
different &lt;code&gt;pip&lt;/code&gt; file-manifest issue on the 3.2 → 3.3 hop and notes &lt;code&gt;uv&lt;/code&gt; is&lt;br&gt;
unaffected by &lt;em&gt;that&lt;/em&gt; one; this is a distinct problem, and I hit it with &lt;code&gt;pip -U&lt;/code&gt; —&lt;br&gt;
I didn't test &lt;code&gt;uv pip install -U&lt;/code&gt;.)&lt;/p&gt;

&lt;p&gt;The fix, either way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; pip uninstall &lt;span class="nt"&gt;-y&lt;/span&gt; fastmcp fastmcp-slim
python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install &lt;/span&gt;fastmcp   &lt;span class="c"&gt;# or fastmcp==4.0.x to pin the version you tested&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or just recreate the venv. It cost me a false-alarm debugging session — twice,&lt;br&gt;
because the symptom (&lt;code&gt;ModuleNotFoundError&lt;/code&gt; on a submodule that's genuinely in the&lt;br&gt;
wheel) is so convincing.&lt;/p&gt;

&lt;p&gt;Also: &lt;code&gt;fastmcp&lt;/code&gt; in 4.x &lt;strong&gt;no longer exposes &lt;code&gt;__version__&lt;/code&gt;&lt;/strong&gt;. If you assert on it&lt;br&gt;
anywhere, switch to &lt;code&gt;importlib.metadata.version("fastmcp")&lt;/code&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  2. &lt;code&gt;httpx&lt;/code&gt; → &lt;code&gt;httpx2&lt;/code&gt;: your &lt;code&gt;except&lt;/code&gt; clauses go quiet
&lt;/h2&gt;

&lt;p&gt;FastMCP 4 dropped &lt;code&gt;httpx&lt;/code&gt; for &lt;code&gt;httpx2&lt;/code&gt; (a next-gen fork) internally. So a FastMCP&lt;br&gt;
client call that used to raise &lt;code&gt;httpx.ConnectError&lt;/code&gt; now raises&lt;br&gt;
&lt;code&gt;httpx2.ConnectError&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The trap: &lt;code&gt;httpx&lt;/code&gt; is still transitively installed in most environments, so this&lt;br&gt;
keeps importing and type-checking:&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="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;StreamableHttpTransport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;do_thing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConnectError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="c1"&gt;# never matches on FastMCP 4
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It just silently stops catching. Grep for &lt;code&gt;except httpx.&lt;/code&gt; and check whether each&lt;br&gt;
one wraps a FastMCP &lt;code&gt;Client&lt;/code&gt; / transport call — if it does, migrate it to&lt;br&gt;
&lt;code&gt;httpx2&lt;/code&gt; (or catch FastMCP's own &lt;code&gt;fastmcp.exceptions.ToolError&lt;/code&gt;, which is usually&lt;br&gt;
what you actually want). Your own direct &lt;code&gt;httpx&lt;/code&gt; calls are unaffected as long as&lt;br&gt;
you keep &lt;code&gt;httpx&lt;/code&gt; as a dependency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Same silent class, elsewhere:&lt;/strong&gt; anything you hand &lt;em&gt;into&lt;/em&gt; FastMCP that's built on&lt;br&gt;
&lt;code&gt;httpx&lt;/code&gt; — a custom &lt;code&gt;httpx_client_factory&lt;/code&gt;, an &lt;code&gt;httpx.AsyncClient&lt;/code&gt; passed to a&lt;br&gt;
transport, an &lt;code&gt;httpx.Auth&lt;/code&gt; — now needs to be &lt;code&gt;httpx2&lt;/code&gt;. The official guide lists&lt;br&gt;
this right next to the &lt;code&gt;except&lt;/code&gt; trap.&lt;/p&gt;

&lt;p&gt;One more downstream effect: TLS verification now uses the OS trust store via&lt;br&gt;
&lt;code&gt;truststore&lt;/code&gt; (honouring &lt;code&gt;SSL_CERT_FILE&lt;/code&gt; / &lt;code&gt;SSL_CERT_DIR&lt;/code&gt;) instead of bundled&lt;br&gt;
&lt;code&gt;certifi&lt;/code&gt; — corporate-CA setups may verify differently. HTTP log records also move&lt;br&gt;
from &lt;code&gt;httpx&lt;/code&gt; / &lt;code&gt;httpcore.*&lt;/code&gt; to &lt;code&gt;httpx2&lt;/code&gt; / &lt;code&gt;httpcore2.*&lt;/code&gt; — update logging filters.&lt;/p&gt;


&lt;h2&gt;
  
  
  3. &lt;code&gt;Client&lt;/code&gt; now defaults to &lt;code&gt;mode="auto"&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;In 4.x, &lt;code&gt;Client(...)&lt;/code&gt; defaults to &lt;code&gt;mode="auto"&lt;/code&gt; and negotiates the modern&lt;br&gt;
&lt;code&gt;2026-07-28&lt;/code&gt; protocol era. That era is sessionless, and it changes runtime&lt;br&gt;
behaviour even though your code compiles fine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No &lt;code&gt;on_initialize&lt;/code&gt; handshake&lt;/strong&gt; — middleware / init hooks tied to it never run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ctx.set_state()&lt;/code&gt; doesn't persist&lt;/strong&gt; to the next call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ctx.elicit()&lt;/code&gt; raises&lt;/strong&gt; — the modern era has no server-initiated back-channel.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your client only does plain reads and writes (&lt;code&gt;call_tool&lt;/code&gt;, &lt;code&gt;read_resource&lt;/code&gt;),&lt;br&gt;
you're fine — that's the common case and it needs no change. If it relies on&lt;br&gt;
session state, an init hook, or elicitation, pin it back:&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="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;legacy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;StreamableHttpTransport&lt;/code&gt; also dropped &lt;code&gt;sse_read_timeout=&lt;/code&gt; — pass &lt;code&gt;timeout=&lt;/code&gt; on the&lt;br&gt;
&lt;code&gt;Client&lt;/code&gt; instead.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Removed &lt;code&gt;ctx&lt;/code&gt; methods
&lt;/h2&gt;

&lt;p&gt;These are gone and raise &lt;code&gt;AttributeError&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ctx.sample()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctx.sample_step()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctx.list_roots()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your server's job was to borrow the caller's model via &lt;code&gt;ctx.sample()&lt;/code&gt; (or&lt;br&gt;
&lt;code&gt;FastMCP(sampling_handler=...)&lt;/code&gt;, also removed), you either call an LLM directly&lt;br&gt;
from the server now or stay on 3.x. &lt;code&gt;ctx.elicit()&lt;/code&gt; still exists but requires a&lt;br&gt;
&lt;code&gt;response_type&lt;/code&gt; argument and raises on modern connections — rewrite it as a guard&lt;br&gt;
tool that returns an "input required" result, or branch on&lt;br&gt;
&lt;code&gt;ctx.request_context.protocol_version&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Background tasks moved to an extension.&lt;/strong&gt; &lt;code&gt;@mcp.tool(task=True)&lt;/code&gt; no longer runs&lt;br&gt;
anything by itself — install &lt;code&gt;fastmcp[tasks]&lt;/code&gt; and register&lt;br&gt;
&lt;code&gt;mcp.add_extension(TasksExtension())&lt;/code&gt;, or startup raises. Drop &lt;code&gt;task=&lt;/code&gt; from&lt;br&gt;
&lt;code&gt;@mcp.resource&lt;/code&gt; / &lt;code&gt;@mcp.prompt&lt;/code&gt; (tools only).&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Version floors
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# hard requirement — resolution fails without it&lt;/span&gt;
&lt;span class="py"&gt;pydantic&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="py"&gt;"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;2.12&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;
&lt;span class="c"&gt;# only if you use the server's FastAPI extra&lt;/span&gt;
&lt;span class="py"&gt;starlette&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="py"&gt;"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="s"&gt;"    # → FastAPI &amp;gt;= 0.133.0 (first version admitting Starlette 1.x)&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pin style unchanged: an &lt;strong&gt;app&lt;/strong&gt; pins the exact version it tested&lt;br&gt;
(&lt;code&gt;fastmcp==4.0.x&lt;/code&gt;); a &lt;strong&gt;library&lt;/strong&gt; floors at &lt;code&gt;fastmcp&amp;gt;=4.0.0&lt;/code&gt; in its own&lt;br&gt;
dependencies and tests against the current release.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Import moves (quick reference)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;3.x&lt;/th&gt;
&lt;th&gt;4.x&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;from fastmcp.tools.tool import Tool, ToolResult&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;from fastmcp.tools import Tool, ToolResult&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;from fastmcp.resources.resource import Resource&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;from fastmcp.resources import Resource&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;TextContent&lt;/code&gt;, &lt;code&gt;Tool&lt;/code&gt; protocol types from &lt;code&gt;fastmcp.types&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;from mcp.types import ...&lt;/code&gt; (&lt;code&gt;fastmcp.types&lt;/code&gt; now holds only FastMCP-defined types)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mcp.as_proxy(sub)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;create_proxy(sub)&lt;/code&gt; from &lt;code&gt;fastmcp.server&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mcp.import_server(sub)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;mcp.mount(sub)&lt;/code&gt; (live composition, not a snapshot)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mcp.add_tool_transformation(name, cfg)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mcp.add_transform(ToolTransform({name: cfg}))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;CachableToolResult&lt;/code&gt; (old typo)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;CacheableToolResult&lt;/code&gt; — no compat alias&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;McpError(ErrorData(code=..., message=...))&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;McpError(code=..., message=...)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;SDK v2 also renamed model fields camelCase → snake_case (&lt;code&gt;inputSchema&lt;/code&gt; →&lt;br&gt;
&lt;code&gt;input_schema&lt;/code&gt;, &lt;code&gt;isError&lt;/code&gt; → &lt;code&gt;is_error&lt;/code&gt;). Old reads are auto-bridged and emit a&lt;br&gt;
&lt;code&gt;FastMCPDeprecationWarning&lt;/code&gt;. The bridge is&lt;br&gt;
&lt;code&gt;fastmcp.settings.mcp_camelcase_compat&lt;/code&gt; (env &lt;code&gt;FASTMCP_MCP_CAMELCASE_COMPAT&lt;/code&gt;),&lt;br&gt;
&lt;code&gt;bool&lt;/code&gt;, default &lt;code&gt;true&lt;/code&gt;. Set it &lt;code&gt;false&lt;/code&gt; once — that turns every remaining camelCase&lt;br&gt;
read into a hard error, so you can find and clear them before the bridge is&lt;br&gt;
removed.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. New defaults from the settings page
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://gofastmcp.com/more/settings" rel="noopener noreferrer"&gt;&lt;code&gt;gofastmcp.com/more/settings&lt;/code&gt;&lt;/a&gt; lists every&lt;br&gt;
setting — each has a &lt;code&gt;fastmcp.settings.&amp;lt;name&amp;gt;&lt;/code&gt; attribute and a &lt;code&gt;FASTMCP_&amp;lt;NAME&amp;gt;&lt;/code&gt;&lt;br&gt;
environment variable. Three defaults changed behaviour in 4.x and don't get a&lt;br&gt;
line in the upgrade guide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;telemetry_mode&lt;/code&gt;&lt;/strong&gt; defaults to &lt;code&gt;"native"&lt;/code&gt; — FastMCP 4 auto-instruments
OpenTelemetry spans for MCP calls. If you don't want that,
&lt;code&gt;FASTMCP_TELEMETRY_MODE=off&lt;/code&gt; (or &lt;code&gt;propagation_only&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;check_for_updates&lt;/code&gt;&lt;/strong&gt; defaults to &lt;code&gt;"stable"&lt;/code&gt; — the CLI checks PyPI for a newer
FastMCP on startup. Set &lt;code&gt;FASTMCP_CHECK_FOR_UPDATES=off&lt;/code&gt; in CI and containers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;client_raise_first_exceptiongroup_error&lt;/code&gt;&lt;/strong&gt; defaults to &lt;code&gt;true&lt;/code&gt; — a client
error surfaces as the first underlying exception, not the &lt;code&gt;ExceptionGroup&lt;/code&gt;.
That's why &lt;code&gt;except ToolError:&lt;/code&gt; still works; if you were catching with &lt;code&gt;except*&lt;/code&gt;,
revisit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also worth a look while you're there: &lt;code&gt;stateless_http&lt;/code&gt; (new-transport-per-request,&lt;br&gt;
the sessionless/Cloud-Run knob), &lt;code&gt;http_host_origin_protection&lt;/code&gt; (new, opt-in Host/&lt;br&gt;
Origin validation for Streamable HTTP), and &lt;code&gt;mask_error_details&lt;/code&gt; (default &lt;code&gt;false&lt;/code&gt;&lt;br&gt;
— error text is passed through unless you raise an explicit &lt;code&gt;ToolError&lt;/code&gt; /&lt;br&gt;
&lt;code&gt;ResourceError&lt;/code&gt; / &lt;code&gt;PromptError&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Keep &lt;code&gt;FASTMCP_DEPRECATION_WARNINGS=true&lt;/code&gt; (the default) for the whole migration —&lt;br&gt;
it's how you find the rest of this list in your own code.&lt;/p&gt;




&lt;h2&gt;
  
  
  The good news: the minimal server barely changes
&lt;/h2&gt;

&lt;p&gt;If your server is only &lt;code&gt;@mcp.tool&lt;/code&gt;-decorated functions plus&lt;br&gt;
&lt;code&gt;mcp.run(transport="stdio")&lt;/code&gt; or &lt;code&gt;mcp.run(transport="streamable-http")&lt;/code&gt;, there is&lt;br&gt;
&lt;strong&gt;no code change&lt;/strong&gt;. The constructor, the decorator, and the transport call are all&lt;br&gt;
the same. You:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;bump &lt;code&gt;pydantic&lt;/code&gt; (and FastAPI, if you use it),&lt;/li&gt;
&lt;li&gt;grep for &lt;code&gt;except httpx.&lt;/code&gt; and migrate the ones around FastMCP calls,&lt;/li&gt;
&lt;li&gt;run your tests.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I actually changed
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Shape&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;An MCP server — &lt;code&gt;@mcp.tool&lt;/code&gt; + &lt;code&gt;mcp.run("stdio" / "streamable-http")&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;dependency floor only — &lt;strong&gt;zero code&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Two MCP clients — &lt;code&gt;Client&lt;/code&gt; + &lt;code&gt;StreamableHttpTransport&lt;/code&gt; + &lt;code&gt;except ToolError&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;dependency floor only — verified &lt;code&gt;mode="auto"&lt;/code&gt; is fine for plain reads / writes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;No API changes in either. The real cost was the &lt;code&gt;pip install -U&lt;/code&gt; false alarm&lt;br&gt;
(twice) and one test that hard-coded a version string in an assertion.&lt;/p&gt;




&lt;h2&gt;
  
  
  The checklist
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ ] Recreate the venv (or `pip3 uninstall fastmcp fastmcp-slim` first) — don't `-U` over 3.x
[ ] pydantic &amp;gt;= 2.12   (+ FastAPI &amp;gt;= 0.133.0 if you use the server's FastAPI extra)
[ ] grep `except httpx.` — migrate the ones wrapping FastMCP Client/transport calls to httpx2
[ ] grep `httpx_client_factory` / `httpx.AsyncClient` / `httpx.Auth` handed to FastMCP — same, → httpx2
[ ] grep `ctx.sample` / `ctx.sample_step` / `ctx.list_roots` — removed (and `FastMCP(sampling_handler=)`)
[ ] grep `ctx.elicit` — needs response_type + fails on modern connections
[ ] grep `@mcp.tool(task=True)` — now needs fastmcp[tasks] + TasksExtension()
[ ] grep `Client(` — needs mode="legacy" only if it relies on session state / on_initialize / elicit
[ ] grep `sse_read_timeout` — moved to Client(timeout=...)
[ ] grep imports: fastmcp.tools.tool, fastmcp.resources.resource, fastmcp.types, mcp.as_proxy, import_server
[ ] grep `fastmcp.__version__` — gone; use importlib.metadata.version("fastmcp")
[ ] set `fastmcp.settings.mcp_camelcase_compat = False` once — clear the camelCase deprecation warnings
[ ] CI: `FASTMCP_CHECK_FOR_UPDATES=off`; decide on `FASTMCP_TELEMETRY_MODE` (default is `native` = OTel on)
[ ] keep `FASTMCP_DEPRECATION_WARNINGS=true` (default) for the whole migration
[ ] run the test suite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're just &lt;code&gt;@mcp.tool&lt;/code&gt; + &lt;code&gt;mcp.run&lt;/code&gt;, the whole list is "bump two floors and&lt;br&gt;
check your &lt;code&gt;httpx&lt;/code&gt; catches." Everything else is for the code that does more.&lt;/p&gt;




&lt;h2&gt;
  
  
  More reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://gofastmcp.com/getting-started/upgrading/from-fastmcp-3" rel="noopener noreferrer"&gt;Upgrading from FastMCP 3&lt;/a&gt; — the official guide this checklist rides on.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://gofastmcp.com/more/settings" rel="noopener noreferrer"&gt;FastMCP settings&lt;/a&gt; — every &lt;code&gt;fastmcp.settings.*&lt;/code&gt; / &lt;code&gt;FASTMCP_*&lt;/code&gt; knob, including the §7 defaults.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://faf.one/mcp" rel="noopener noreferrer"&gt;The MCP landscape&lt;/a&gt; — how MCP servers are built (the official SDKs, FastMCP) and how they run (&lt;code&gt;stdio&lt;/code&gt;, Streamable HTTP), and where the &lt;code&gt;2026-07-28&lt;/code&gt; sessionless shift sits.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>fastapi</category>
      <category>mcp</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>React, Next.js, Svelte, Zod: none of them can tell AI who they're actually for</title>
      <dc:creator>wolfejam.dev</dc:creator>
      <pubDate>Wed, 02 Sep 2026 15:40:01 +0000</pubDate>
      <link>https://dev.to/wolfejam/react-nextjs-svelte-zod-none-of-them-can-tell-ai-who-theyre-actually-for-47eo</link>
      <guid>https://dev.to/wolfejam/react-nextjs-svelte-zod-none-of-them-can-tell-ai-who-theyre-actually-for-47eo</guid>
      <description>&lt;h2&gt;
  
  
  Your coding agent is good at reading code.
&lt;/h2&gt;

&lt;p&gt;Point Claude Code or Cursor at a repo&lt;br&gt;
and it will figure out the language, the framework, the build command — it just costs you tokens and a few tool calls every session to re-derive what it forgot.&lt;/p&gt;

&lt;p&gt;What it &lt;em&gt;can't&lt;/em&gt; read is the part that isn't in the code: who the project is for,and why it exists. So it guesses. Confidently, in the same tone it uses for the facts it actually verified.&lt;/p&gt;

&lt;p&gt;I wanted to see how big that gap is on real projects, so I ran a mechanical context extractor over eight of the most-loved repos in the JavaScript world.&lt;/p&gt;
&lt;h2&gt;
  
  
  The method
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;faf git &amp;lt;url&amp;gt;&lt;/code&gt; clones a repo and fills in a small typed context file (&lt;code&gt;project.faf&lt;/code&gt;) from what it can find — README, &lt;code&gt;package.json&lt;/code&gt;, project structure, config. No hand-authoring, no LLM writing prose. It fills what's there and leaves the rest blank. Nine-ish slots: the identity (name, goal, language) and the six W's — who, what, why, where, when, how.&lt;/p&gt;

&lt;p&gt;Run it yourself: &lt;code&gt;npx faf-cli git https://github.com/facebook/react&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;repo&lt;/th&gt;
&lt;th&gt;extracted&lt;/th&gt;
&lt;th&gt;&lt;code&gt;who&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;why&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;facebook/react&lt;/td&gt;
&lt;td&gt;56%&lt;/td&gt;
&lt;td&gt;— blank —&lt;/td&gt;
&lt;td&gt;— blank —&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;vercel/next.js&lt;/td&gt;
&lt;td&gt;44%&lt;/td&gt;
&lt;td&gt;— blank —&lt;/td&gt;
&lt;td&gt;— blank —&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;expressjs/express&lt;/td&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;td&gt;— blank —&lt;/td&gt;
&lt;td&gt;— blank —&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;colinhacks/zod&lt;/td&gt;
&lt;td&gt;67%&lt;/td&gt;
&lt;td&gt;— blank —&lt;/td&gt;
&lt;td&gt;— blank —&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sveltejs/svelte&lt;/td&gt;
&lt;td&gt;88%&lt;/td&gt;
&lt;td&gt;— blank —&lt;/td&gt;
&lt;td&gt;— blank —&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;prettier/prettier&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;td&gt;— blank —&lt;/td&gt;
&lt;td&gt;— blank —&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Eight repos in total (React, Next.js, Express, Zod, Hono, Svelte, Vue, Prettier). Every one of them: &lt;strong&gt;&lt;code&gt;who is this for&lt;/code&gt; and &lt;code&gt;why does this exist&lt;/code&gt; came back empty.&lt;/strong&gt; Not one has that written anywhere a machine — or an agent at task time — can read it.&lt;/p&gt;

&lt;p&gt;Svelte scored 88%. The &lt;code&gt;who&lt;/code&gt; and &lt;code&gt;why&lt;/code&gt; are still blank. This isn't a documentation-quality problem, and it's not a knock on any of these projects. The stack lives in the files. The intent lives in maintainers' heads, design docs, old RFC threads, and Discord history — none of which your agent has open when it's editing a file.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why the two halves behave differently
&lt;/h2&gt;

&lt;p&gt;The scores range from 44% to 88%, and that whole spread is one thing: &lt;strong&gt;how much stack the repo exposes in config files.&lt;/strong&gt; Svelte's monorepo setup fills seven stack slots (framework, runtime, build, CI…) and lands at 88%. React's root exposes one, and lands at 56%. More config → higher score.&lt;/p&gt;

&lt;p&gt;So the &lt;em&gt;recoverable&lt;/em&gt; half — language, framework, build — varies 2× across these repos. A cold agent can dig all of it out; you just pay for the dig, every session, in tokens and latency.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;unrecoverable&lt;/em&gt; half doesn't vary at all. &lt;code&gt;who&lt;/code&gt; and &lt;code&gt;why&lt;/code&gt; came back empty in &lt;strong&gt;every one of the eight&lt;/strong&gt;, regardless of repo size, fame, or documentation.&lt;br&gt;
No amount of spelunking through &lt;code&gt;src/&lt;/code&gt; tells you that a library was built to replace one specific painful pattern, or that it targets library authors and not app developers, or that an architectural choice you're about to "clean up" was deliberate. That information was never committed in a form the agent can consume.&lt;/p&gt;

&lt;p&gt;So the agent fills the blank with a plausible story. On React it'll probably land close. On your internal service, or a library with a subtle audience, it won't — and it'll refactor accordingly, with confidence.&lt;/p&gt;
&lt;h2&gt;
  
  
  The fix is about ten minutes
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;faf go&lt;/code&gt; walks a maintainer through the six questions once, writes the answers into &lt;code&gt;project.faf&lt;/code&gt;, and you commit it. From then on every agent — Claude Code, Cursor, Codex — reads the same authored context instead of guessing, and it's one file that projects out to &lt;code&gt;CLAUDE.md&lt;/code&gt;, &lt;code&gt;AGENTS.md&lt;/code&gt;, &lt;code&gt;.cursor/rules/&lt;/code&gt; so&lt;br&gt;
those can't drift apart.&lt;/p&gt;

&lt;p&gt;faf-cli's own &lt;code&gt;project.faf&lt;/code&gt; looked much like these until someone sat down and filled it in. React's team could close their gap in a single commit.&lt;/p&gt;

&lt;p&gt;(The first &lt;code&gt;project.faf&lt;/code&gt; I ever wrote was for a Svelte app — which makes it a little funny that &lt;code&gt;sveltejs/svelte&lt;/code&gt; topped this table at 88% and still can't tell an agent who Svelte is for.)&lt;/p&gt;
&lt;h2&gt;
  
  
  If you want the number
&lt;/h2&gt;

&lt;p&gt;Fill the gaps first with &lt;code&gt;faf go&lt;/code&gt;, then &lt;code&gt;faf bench&lt;/code&gt; measures the delta directly: it asks a model the questions about your repo cold, then again after reading &lt;code&gt;project.faf&lt;/code&gt;, and grades the answers mechanically (no LLM judge). Questions are derived from the &lt;em&gt;populated&lt;/em&gt; slots — the &lt;code&gt;.faf&lt;/code&gt; is the answer key — so a repo that still has &lt;code&gt;who&lt;/code&gt;/&lt;code&gt;why&lt;/code&gt; blank can't be graded on them; that's what &lt;code&gt;faf go&lt;/code&gt;is for. The delta is the accuracy your agent is leaving on the table, and where the cold misses cluster tells you which parts of your project only exist in your head.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx faf-cli go                     &lt;span class="c"&gt;# fill project.faf (the ten minutes)&lt;/span&gt;
npx faf-cli bench questions        &lt;span class="c"&gt;# the questions, derived from your repo&lt;/span&gt;
npx faf-cli bench grade answers.json &lt;span class="nt"&gt;--cold&lt;/span&gt;
npx faf-cli bench grade answers.json &lt;span class="nt"&gt;--faf&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A low cold score isn't a verdict on the model. It's a diagnosis that the project is under-described for an agent to work in — and the output ends in &lt;code&gt;faf go&lt;/code&gt;, not a leaderboard.&lt;/p&gt;




&lt;p&gt;Notes: &lt;code&gt;faf git&lt;/code&gt;'s percentage reflects how much is &lt;em&gt;in the repo&lt;/em&gt;, so a high score means good docs, not a good tool — and the &lt;code&gt;who&lt;/code&gt;/&lt;code&gt;why&lt;/code&gt; blanks are honest, the information genuinely isn't there to extract. &lt;code&gt;project.faf&lt;/code&gt; is one typed source (the &lt;code&gt;.faf&lt;/code&gt; format is an IANA-registered media type,&lt;code&gt;application/vnd.faf+yaml&lt;/code&gt;) that the per-tool context files are generated from, rather than another markdown file to maintain by hand.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>claude</category>
      <category>cursor</category>
    </item>
    <item>
      <title>Your tools/list is stamped. That is not the same as cached.</title>
      <dc:creator>wolfejam.dev</dc:creator>
      <pubDate>Thu, 20 Aug 2026 11:00:00 +0000</pubDate>
      <link>https://dev.to/wolfejam/your-toolslist-is-stamped-that-is-not-the-same-as-cached-2j0f</link>
      <guid>https://dev.to/wolfejam/your-toolslist-is-stamped-that-is-not-the-same-as-cached-2j0f</guid>
      <description>&lt;p&gt;A stamp on &lt;code&gt;tools/list&lt;/code&gt; is a claim about later reuse. Presence is not proof the next call is cacheable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — The 7/28 spec put &lt;code&gt;ttlMs&lt;/code&gt; and &lt;code&gt;cacheScope&lt;/code&gt; on &lt;code&gt;tools/list&lt;/code&gt;. Most write-ups treat that stamp as a cache. It is a &lt;strong&gt;claim about later reuse&lt;/strong&gt;. Presence means the server &lt;em&gt;said&lt;/em&gt; something. Truth needs an observation that can fail. We shipped a liar and a probe. The catalog grew. The probe still said OK. The stamp half was along for the ride. One mutant per clause. Name which negatives are still live.&lt;/p&gt;




&lt;h2&gt;
  
  
  The victory lap
&lt;/h2&gt;

&lt;p&gt;The 2026-07-28 spec added list-cache stamps (SEP-2549), modeled on HTTP &lt;code&gt;Cache-Control&lt;/code&gt;. The official sentence is some version of: &lt;em&gt;clients know exactly how long &lt;code&gt;tools/list&lt;/code&gt; is fresh.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That sentence is doing a lot of work.&lt;/p&gt;

&lt;p&gt;HTTP already taught this. &lt;code&gt;Cache-Control: max-age=60&lt;/code&gt; is a statement. It is not proof the next GET returns the same bytes. MCP imported the words. It did not import a test.&lt;/p&gt;

&lt;p&gt;A server can print &lt;code&gt;ttlMs: 60000, cacheScope: Public&lt;/code&gt; and change its catalog on the next call. The stamp is still well-formed. A probe that only checks &lt;em&gt;presence&lt;/em&gt; will still pass.&lt;/p&gt;




&lt;h2&gt;
  
  
  We built a liar. Then the lie got lazy.
&lt;/h2&gt;

&lt;p&gt;Last piece: a labeled companion, &lt;code&gt;mcp-worse&lt;/code&gt;, and one command — &lt;code&gt;contrast-smoke&lt;/code&gt; — that passes only if the good server meets the BETTER list contract &lt;strong&gt;and&lt;/strong&gt; the bad one fails it.&lt;/p&gt;

&lt;p&gt;The checker looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;is_lying_surface&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ListProbe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;unstamped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="py"&gt;.ttl_ms&lt;/span&gt;&lt;span class="nf"&gt;.is_none&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="py"&gt;.cache_scope&lt;/span&gt;&lt;span class="nf"&gt;.is_none&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;wrong_order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="py"&gt;.names&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nf"&gt;better_names&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;unstamped&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;wrong_order&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;OR&lt;/code&gt; is the hole.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mcp-better&lt;/code&gt; grew a third tool, &lt;code&gt;confirm_echo&lt;/code&gt;. &lt;code&gt;mcp-worse&lt;/code&gt; still lists two: &lt;code&gt;echo&lt;/code&gt;, &lt;code&gt;health&lt;/code&gt;. So &lt;code&gt;wrong_order&lt;/code&gt; (really: names ≠ the good catalog) is &lt;strong&gt;always true&lt;/strong&gt;. The stamp half stopped carrying observable weight.&lt;/p&gt;

&lt;p&gt;If worse grew &lt;code&gt;with_ttl_ms&lt;/code&gt; and &lt;code&gt;with_cache_scope&lt;/code&gt; tomorrow, the example would still print OK. It would fail for contents only. The negative case for &lt;code&gt;ttlMs&lt;/code&gt; would be gone, and nothing would say so.&lt;/p&gt;

&lt;p&gt;The “companion must stay a reliable liar” guard only fires when &lt;strong&gt;every&lt;/strong&gt; clause goes green at once. Partial decay is invisible.&lt;/p&gt;

&lt;p&gt;A comment on that post named it. They were right.&lt;/p&gt;

&lt;p&gt;A liar that fails for two reasons is weaker evidence than two liars that each fail for one. Steal-the-pattern already said: one smallest lie per claim. We had one binary that violated every clause, and an &lt;code&gt;OR&lt;/code&gt; that hid which ones were still live.&lt;/p&gt;




&lt;h2&gt;
  
  
  Presence is not truth
&lt;/h2&gt;

&lt;p&gt;Order is a property of the list the probe is &lt;strong&gt;holding&lt;/strong&gt;. You can see it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ttlMs&lt;/code&gt; and &lt;code&gt;cacheScope&lt;/code&gt; are statements about how that list &lt;strong&gt;may be reused later&lt;/strong&gt;. Confirming the fields exist and look well-formed proves the server made a claim. It does not prove a client would be right to skip the next &lt;code&gt;tools/list&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;order-restart-smoke&lt;/code&gt; does the right thing for &lt;strong&gt;order&lt;/strong&gt;: two processes, same names. It applies that shape to TTL as &lt;code&gt;ttl_a == ttl_b&lt;/code&gt; — the stamp is restart-stable. That is a property of the &lt;strong&gt;number&lt;/strong&gt;, not of the caching behavior the number describes.&lt;/p&gt;

&lt;p&gt;Falsifying a TTL claim takes an observation pair that straddles a change. Our catalog is compiled in. The TTL claim &lt;strong&gt;cannot&lt;/strong&gt; be violated yet. Declared, not falsified. Once the catalog goes dynamic, &lt;code&gt;ttlMs&lt;/code&gt; is the first stamp with room to lie — and it is the clause with the least behind it.&lt;/p&gt;

&lt;p&gt;This lab reads &lt;strong&gt;this&lt;/strong&gt; list. Not the next call. Not a client cache.&lt;/p&gt;




&lt;h2&gt;
  
  
  Run the audit
&lt;/h2&gt;

&lt;p&gt;The probe now requires &lt;strong&gt;each&lt;/strong&gt; teaching clause on the companion. Stamp decay fails closed and names the clause.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// examples/contrast_smoke.rs — current tree&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;is_unstamped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ListProbe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="py"&gt;.ttl_ms&lt;/span&gt;&lt;span class="nf"&gt;.is_none&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="py"&gt;.cache_scope&lt;/span&gt;&lt;span class="nf"&gt;.is_none&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;wrong_names&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ListProbe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="py"&gt;.names&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nf"&gt;better_names&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both must stay true on &lt;code&gt;mcp-worse&lt;/code&gt;. If either goes green, the example exits non-zero and says which.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1 — Clone and build both binaries
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Wolfe-Jam/mcp-better.git
&lt;span class="nb"&gt;cd &lt;/span&gt;mcp-better
cargo build &lt;span class="nt"&gt;--bins&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the &lt;strong&gt;tree&lt;/strong&gt;, not &lt;code&gt;cargo install mcp-better&lt;/code&gt;. The published &lt;code&gt;v0.5.0&lt;/code&gt; tag still has the &lt;code&gt;OR&lt;/code&gt;. The named-clause OK line is the honesty cut on current &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 — Run contrast-smoke
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo run &lt;span class="nt"&gt;--example&lt;/span&gt; contrast-smoke
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expect (captured 2026-08-19, current tree):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;better names=["health", "echo", "confirm_echo"] ttl=Some(60000) scope=Some(Public)
worse  names=["echo", "health"]                 ttl=None        scope=None
contrast-smoke: OK (better contract · worse unstamped · worse names≠health,echo,confirm_echo)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The OK line is the point. It names which negatives are still live.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 — What you just proved
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Claim&lt;/th&gt;
&lt;th&gt;Evidence&lt;/th&gt;
&lt;th&gt;Not proved&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Good list is ordered + stamped&lt;/td&gt;
&lt;td&gt;Wire: three names, &lt;code&gt;ttlMs &amp;gt; 0&lt;/code&gt;, &lt;code&gt;cacheScope == Public&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;That a client should cache it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Companion stays unstamped&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;unstamped&lt;/code&gt; asserted, not &lt;code&gt;OR&lt;/code&gt;-ed away&lt;/td&gt;
&lt;td&gt;Cache behavior&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Companion catalog ≠ better&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;names ≠ health,echo,confirm_echo&lt;/code&gt; (today: two tools, reversed)&lt;/td&gt;
&lt;td&gt;That “wrong names” is &lt;em&gt;only&lt;/em&gt; order&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Partial decay is visible&lt;/td&gt;
&lt;td&gt;Stamp clause going green fails the example&lt;/td&gt;
&lt;td&gt;A second mutant (stamped-but-reversed)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That last empty cell is honest. One companion that fails for two reasons is still weaker than two mutants that each fail for one. This cut makes the live negatives &lt;strong&gt;named&lt;/strong&gt;. It does not ship a second dummy.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this is not
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Not a cache test. No observation pair straddling a change. The catalog is compiled in.&lt;/li&gt;
&lt;li&gt;Not a rewrite of the last post. That one built the liar. This one asks what the probe actually falsified.&lt;/li&gt;
&lt;li&gt;Not a version diary. No new tool. No new crate.&lt;/li&gt;
&lt;li&gt;Not “every BETTER server must implement a mutant factory.”&lt;/li&gt;
&lt;li&gt;Not a security scanner.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Steal the pattern
&lt;/h2&gt;

&lt;p&gt;Same shape as last time — finish the last step:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write down every claim the docs make about the &lt;strong&gt;wire&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;For each claim, the smallest change that would make it false.&lt;/li&gt;
&lt;li&gt;One mutant per claim — or, at minimum, &lt;strong&gt;assert each clause&lt;/strong&gt; so one leftover violation cannot hide another.&lt;/li&gt;
&lt;li&gt;Print which negatives are still live. An OK line that does not name them is an &lt;code&gt;OR&lt;/code&gt; in disguise.&lt;/li&gt;
&lt;li&gt;If the claim is about &lt;strong&gt;later reuse&lt;/strong&gt; (&lt;code&gt;ttlMs&lt;/code&gt;, &lt;code&gt;cacheScope&lt;/code&gt;), a presence check is “a claim was made.” Do not call it verified until something can fail.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you cannot say which clause is still live, you do not have a negative case. You have a dummy that is wrong in a pile.&lt;/p&gt;




&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Last post: &lt;a href="https://dev.to/wolfejam/i-built-a-lying-mcp-server-on-purpose-heres-how-you-catch-it-102g"&gt;I built a lying MCP server on purpose&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Era map: &lt;a href="https://dev.to/wolfejam/not-all-mcp-servers-are-equal-what-728-just-made-official-2f29"&gt;Not all MCP servers are equal — what 7/28 just made official&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Spec: &lt;a href="https://modelcontextprotocol.io/specification/2026-07-28" rel="noopener noreferrer"&gt;MCP 2026-07-28&lt;/a&gt; · list cache stamps (SEP-2549)&lt;/li&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/Wolfe-Jam/mcp-better" rel="noopener noreferrer"&gt;github.com/Wolfe-Jam/mcp-better&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Close
&lt;/h2&gt;

&lt;p&gt;A README cannot lie to a test that reads the wire. A stamp can still lie to a README.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ttlMs&lt;/code&gt; on the list is a reuse claim. &lt;code&gt;contrast-smoke&lt;/code&gt; now says which teaching clauses are still live. That is the list. Not the cache.&lt;/p&gt;

&lt;p&gt;Claim = wire. Ask what you actually falsified.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which clause on your &lt;code&gt;tools/list&lt;/code&gt; is only present — not proven?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I'm an AAIF Ambassador. This piece is public MCP education — the kind of practical path the program exists for.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>rust</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>"Never guessed" isn't the same as "never misses."</title>
      <dc:creator>wolfejam.dev</dc:creator>
      <pubDate>Thu, 20 Aug 2026 00:27:17 +0000</pubDate>
      <link>https://dev.to/wolfejam/never-guessed-isnt-the-same-as-never-misses-1okp</link>
      <guid>https://dev.to/wolfejam/never-guessed-isnt-the-same-as-never-misses-1okp</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — I built a tool that authors AGENTS.md from real repo facts, never invented ones. A month after it was scored as a real AAIF contribution: 29 downloads, 2 stars, 0 issues. Nobody stress-tested it, so I did — against two repos I'd already dug into by hand. Every line it wrote was true. It still missed the most important test in both of them, for the same structural reason, twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest starting number
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;agents-md-facts&lt;/code&gt; authors a minimal AGENTS.md from what your repo actually declares — real build commands, real test commands, real file layout. Nothing guessed. It was submitted to AAIF as a project contribution and scored accordingly.&lt;/p&gt;

&lt;p&gt;A month later: 29 npm downloads, 2 GitHub stars, 0 issues, 0 forks. Five commits since launch, all docs and housekeeping — nobody used it hard enough to find something to fix.&lt;/p&gt;

&lt;p&gt;That's not a failure post. It's the honest premise for this one: if nobody else was going to stress-test it, I would.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dogfooding it for real
&lt;/h2&gt;

&lt;p&gt;I ran it, &lt;code&gt;--dry-run&lt;/code&gt;, against two repos I already understood deeply — not blind spots, ground truth I could check the output against. One Rust, one TypeScript.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;mcp-better &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npx agents-md-facts &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Run the tests&lt;/span&gt;

&lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;bash
&lt;/span&gt;cargo &lt;span class="nb"&gt;test
&lt;/span&gt;cargo clippy
&lt;span class="p"&gt;```&lt;/span&gt;

&lt;span class="gu"&gt;## Definition of Done&lt;/span&gt;

Done when: &lt;span class="sb"&gt;`cargo clippy`&lt;/span&gt; exits 0 · &lt;span class="sb"&gt;`cargo test`&lt;/span&gt; passes · committed with a clear message.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;claude-faf-mcp &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npx agents-md-facts &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Run the tests&lt;/span&gt;

&lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;bash
&lt;/span&gt;npm run &lt;span class="nb"&gt;test
&lt;/span&gt;npm run lint
&lt;span class="p"&gt;```&lt;/span&gt;

&lt;span class="gu"&gt;## Definition of Done&lt;/span&gt;

Done when: &lt;span class="sb"&gt;`npm run lint`&lt;/span&gt; exits 0 · &lt;span class="sb"&gt;`npm run test`&lt;/span&gt; passes · committed with a clear message.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What it got right
&lt;/h2&gt;

&lt;p&gt;Every single line in both outputs traces to something real. &lt;code&gt;cargo test&lt;/code&gt; and &lt;code&gt;cargo clippy&lt;/code&gt; genuinely exist and genuinely run in &lt;code&gt;mcp-better&lt;/code&gt;. &lt;code&gt;npm run test&lt;/code&gt; and &lt;code&gt;npm run lint&lt;/code&gt; genuinely exist and genuinely run in &lt;code&gt;claude-faf-mcp&lt;/code&gt;. Zero invented commands, either time. The tool's actual promise — never guessed — held completely, both times, under real conditions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it missed — twice, same shape
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;mcp-better&lt;/code&gt; ships a tool called &lt;code&gt;confirm_echo&lt;/code&gt; with a real multi-round contract: a sealed, tamper-checked handshake. The unit tests for it live in &lt;code&gt;src/server.rs&lt;/code&gt; and run fine under &lt;code&gt;cargo test&lt;/code&gt;. But the &lt;em&gt;contract&lt;/em&gt; — does the tool still promise what it promised, over the wire, to a real client — is checked by a completely separate command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cargo &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--all-targets&lt;/span&gt;
&lt;span class="go"&gt;   Running unittests examples/mrtr_client.rs
running 0 tests
&lt;/span&gt;&lt;span class="gp"&gt;test result: ok. 0 passed;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;0 failed&lt;span class="p"&gt;;&lt;/span&gt; 0 ignored
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;cargo test&lt;/code&gt; compiles that file. It runs zero test functions in it. The actual check only happens if you separately run &lt;code&gt;cargo run --example mrtr-client&lt;/code&gt; — which is exactly the command the generated AGENTS.md never mentions, because "Definition of Done: &lt;code&gt;cargo test&lt;/code&gt; passes" is what the tool detected, and that line is &lt;em&gt;true&lt;/em&gt;. It's just not the whole truth.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;claude-faf-mcp&lt;/code&gt; has the same shape from a different angle. The generated file correctly points at &lt;code&gt;npm run test&lt;/code&gt;. Sitting in the same &lt;code&gt;tests/&lt;/code&gt; directory, untouched by the generated output: &lt;code&gt;WJTTC-FAFM-MEMORY.md&lt;/code&gt;, &lt;code&gt;WJTTC-MCP-CLI-CONTINUITY-v274.md&lt;/code&gt;, &lt;code&gt;WJTTC-REPORT-MCP-v120-STRESS-TEST.md&lt;/code&gt; — a separate certification layer the tool has no way to surface, for the same reason: it doesn't look like &lt;code&gt;npm run test&lt;/code&gt;, so there's nothing for a facts-detector to grab onto.&lt;/p&gt;

&lt;p&gt;Two different stacks. Same blind spot, in the same place: the deepest verification layer in each repo is precisely the one that doesn't look like every other repo's test command.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why — and it isn't a bug
&lt;/h2&gt;

&lt;p&gt;"Never guessed" means the tool only writes down things it can detect. That's the entire safety property, and it's a real one — it's the whole reason this class of tool doesn't fall into the auto-generated-bloat trap the research on AI-written instruction files warns about. But detection has a shape. The tool knows what &lt;code&gt;cargo test&lt;/code&gt; and &lt;code&gt;npm run test&lt;/code&gt; look like. It has no way to know that &lt;code&gt;cargo run --example mrtr-client&lt;/code&gt; or a hand-written stress report &lt;em&gt;also&lt;/em&gt; answers "is this thing actually verified" — because those don't match any pattern it's built to recognize.&lt;/p&gt;

&lt;p&gt;"Real fact" and "fact my detector recognizes" are not the same set. The gap between them is exactly where the most important line in an AGENTS.md can go missing, silently, while every other line stays true.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means beyond this one tool
&lt;/h2&gt;

&lt;p&gt;Any facts-based generator — this one or the next one — inherits the same limit. A generated AGENTS.md is a floor, not a ceiling: provably not-wrong, because every line traces to something real, which is not the same claim as complete. And the layer most likely to be missing is usually the one that matters most, because a repo's &lt;em&gt;deepest&lt;/em&gt; verification is often exactly the thing that was built custom, in a shape nothing else in that codebase uses — which is precisely what a pattern-matcher is worst at finding.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm changing because of this
&lt;/h2&gt;

&lt;p&gt;The fact-sourcing discipline underneath all of this — every line traces to a fact, verify it stays true in the same PR it changed — is still the right foundation. It just isn't sufficient alone, and pretending otherwise would be exactly the kind of overclaim this series has spent five parts arguing against.&lt;/p&gt;

&lt;p&gt;The honest addition is one more pass, after the tool runs, human or agent: &lt;em&gt;does this repo have a second, differently-shaped verification layer the tool wouldn't have found?&lt;/em&gt; That's the question that surfaced &lt;code&gt;mrtr-client&lt;/code&gt; and the WJTTC reports here. It's not automatable yet. Naming it is the first step toward making it so.&lt;/p&gt;




&lt;p&gt;Help guide what we build — comments and suggestions welcome.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>devtools</category>
      <category>testing</category>
    </item>
    <item>
      <title>"I built a lying MCP server on purpose — here's how you catch it"</title>
      <dc:creator>wolfejam.dev</dc:creator>
      <pubDate>Mon, 17 Aug 2026 05:06:18 +0000</pubDate>
      <link>https://dev.to/wolfejam/i-built-a-lying-mcp-server-on-purpose-heres-how-you-catch-it-102g</link>
      <guid>https://dev.to/wolfejam/i-built-a-lying-mcp-server-on-purpose-heres-how-you-catch-it-102g</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — A server's README can say anything. Its &lt;code&gt;tools/list&lt;/code&gt; response either backs that up or it doesn't.&lt;/p&gt;

&lt;p&gt;I built &lt;strong&gt;&lt;code&gt;mcp-worse&lt;/code&gt;&lt;/strong&gt; — a second binary, sharing two of &lt;code&gt;mcp-better&lt;/code&gt;'s tool names, that deliberately omits the list-cache stamps and serves tools in the wrong order — so a test could prove the difference. That test is &lt;strong&gt;&lt;code&gt;contrast-smoke&lt;/code&gt;&lt;/strong&gt;: one command, real MCP clients, real wire traffic. Exit code 0 only if the good server passes the contract &lt;em&gt;and&lt;/em&gt; the bad one fails it.&lt;/p&gt;

&lt;p&gt;This is what "claim = wire" looks like when you stop saying it and start shipping it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with trusting a README
&lt;/h2&gt;

&lt;p&gt;Every MCP server's docs make claims: &lt;em&gt;stateless&lt;/em&gt;, &lt;em&gt;cacheable list&lt;/em&gt;, &lt;em&gt;stable tool order&lt;/em&gt;. Nothing in the protocol stops a server from claiming all three and doing none of them. The client can't tell from the tool &lt;strong&gt;names&lt;/strong&gt; — &lt;code&gt;health&lt;/code&gt; and &lt;code&gt;echo&lt;/code&gt; look identical whether the server behind them is honest or not.&lt;/p&gt;

&lt;p&gt;So the question isn't "does this server have a &lt;code&gt;tools/list&lt;/code&gt; endpoint." It's: &lt;strong&gt;if the docs are wrong, what breaks, and when?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most servers never answer that, because nothing is &lt;em&gt;built to fail&lt;/em&gt; on purpose. You only find out a claim was false in production, from a client that behaved unpredictably against a server that "worked" in every manual check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build the lie on purpose
&lt;/h2&gt;

&lt;p&gt;The cleanest way to test a contract-checker is to hand it something that violates the contract — not a hypothetical, a real binary.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mcp-worse&lt;/code&gt; is that binary. Same protocol version on the wire, same transport, and it mirrors two of &lt;code&gt;mcp-better&lt;/code&gt;'s tools by name (&lt;code&gt;health&lt;/code&gt;, &lt;code&gt;echo&lt;/code&gt;) — &lt;code&gt;mcp-better&lt;/code&gt; has since grown a third (&lt;code&gt;confirm_echo&lt;/code&gt;, an MRTR retry-flow demo) that the lying companion was never updated to match, so the tool &lt;em&gt;count&lt;/em&gt; alone is now part of the gap too, alongside two deliberate breaks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/worse.rs&lt;/span&gt;
&lt;span class="cd"&gt;/// Intentional anti-order (BETTER is health → echo).&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;WORSE_TOOL_ORDER&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"echo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"health"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="cd"&gt;/// Unstamped list with reversed order — the lie.&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;lying_list_tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ListToolsResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.tool_router&lt;/span&gt;&lt;span class="nf"&gt;.list_all&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="nf"&gt;.sort_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* ...WORSE_TOOL_ORDER... */&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Deliberately omit with_ttl_ms / with_cache_scope.&lt;/span&gt;
    &lt;span class="nn"&gt;ListToolsResult&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with_all_items&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;ttlMs&lt;/code&gt;. No &lt;code&gt;cacheScope&lt;/code&gt;. Tools reversed. The &lt;code&gt;health&lt;/code&gt; tool result even says so out loud:&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;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"server"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mcp-worse"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.4.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"protocol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-28"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"LYING-DEMO"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"warning"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"This binary deliberately fails the BETTER list contract for teaching."&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;It's not a trick client would fall for in the wild — it's labeled, it's teaching-only, it never ships to a registry. Its only job is to be &lt;strong&gt;wrong on purpose, reliably&lt;/strong&gt;, so something else can prove it catches a lie.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run the audit
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;contrast-smoke&lt;/code&gt; spawns both binaries as actual child processes, talks real MCP over stdio, and checks the wire — not the source, not the docs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// examples/contrast_smoke.rs&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;is_better_contract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ListProbe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="py"&gt;.names&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;better_names&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nd"&gt;matches!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="py"&gt;.ttl_ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ms&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;ms&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="py"&gt;.cache_scope&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;CacheScope&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Public&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;is_lying_surface&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ListProbe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;unstamped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="py"&gt;.ttl_ms&lt;/span&gt;&lt;span class="nf"&gt;.is_none&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="py"&gt;.cache_scope&lt;/span&gt;&lt;span class="nf"&gt;.is_none&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;wrong_order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="py"&gt;.names&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nf"&gt;better_names&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;unstamped&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;wrong_order&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 1 — Clone and build both binaries
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Wolfe-Jam/mcp-better.git
&lt;span class="nb"&gt;cd &lt;/span&gt;mcp-better
cargo build &lt;span class="nt"&gt;--bins&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This builds &lt;code&gt;mcp-better&lt;/code&gt; and &lt;code&gt;mcp-worse&lt;/code&gt; side by side — &lt;code&gt;contrast-smoke&lt;/code&gt; needs both on disk to probe them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 — Run contrast-smoke
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo run &lt;span class="nt"&gt;--example&lt;/span&gt; contrast-smoke
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expect (real output, captured 2026-08-16 against v0.4.3):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;better names=["health", "echo", "confirm_echo"] ttl=Some(60000) scope=Some(Public)
worse  names=["echo", "health"]                 ttl=None        scope=None
contrast-smoke: OK (mcp-better passes BETTER list contract · mcp-worse fails it)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read those two lines side by side — that's the whole post in two rows of text. Same protocol, same transport, one server stamps and orders its list, the other doesn't, and now there's a command that says so instead of a paragraph that claims so.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;mcp-better&lt;/code&gt; ever regresses — someone drops the &lt;code&gt;ttlMs&lt;/code&gt; stamp in a refactor, tool order stops being deterministic — this fails loudly, on the &lt;em&gt;good&lt;/em&gt; server, using the exact same probe that already knows what "bad" looks like. And if &lt;code&gt;mcp-worse&lt;/code&gt; ever accidentally started passing the contract, that fails too (the companion has to stay a reliable liar or the test is worthless).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 — What you just proved
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Claim&lt;/th&gt;
&lt;th&gt;Evidence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;mcp-better&lt;/code&gt;'s list is cache-stamped&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ttlMs &amp;gt; 0&lt;/code&gt;, &lt;code&gt;cacheScope == Public&lt;/code&gt;, read off the wire&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tool order is a real contract, not incidental&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;mcp-worse&lt;/code&gt; reversing it is what makes the test fail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The checker isn't fooled by names&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;mcp-worse&lt;/code&gt; shares two tool names with &lt;code&gt;mcp-better&lt;/code&gt; (&lt;code&gt;health&lt;/code&gt;, &lt;code&gt;echo&lt;/code&gt;); wrong order and missing stamps fail it regardless — no name-matching heuristic to fool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The contract has a negative case&lt;/td&gt;
&lt;td&gt;Not just "good passes" — "bad provably fails," same probe&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That last row is the actual point. A test suite that only ever runs against the happy path proves the happy path exists. It doesn't prove the checker &lt;em&gt;works&lt;/em&gt; — that it would catch a violation if one showed up. &lt;code&gt;mcp-worse&lt;/code&gt; exists so &lt;code&gt;contrast-smoke&lt;/code&gt; has something real to fail against, once, in CI, forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this is not
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Not a security scanner — it doesn't check auth, injection, or prompt-level trust. It checks one specific, common claim: does the list response match what the docs say about caching and order.&lt;/li&gt;
&lt;li&gt;Not a general-purpose MCP fuzzer. Two tools, one contract, on purpose — small enough to read in five minutes.&lt;/li&gt;
&lt;li&gt;Not a product. &lt;code&gt;mcp-worse&lt;/code&gt; never ships to the MCP Registry. It exists in the same repo as &lt;code&gt;mcp-better&lt;/code&gt;, for the same reason a crash-test dummy exists next to the car.&lt;/li&gt;
&lt;li&gt;Not "MCP servers are untrustworthy." Most aren't audited this way &lt;em&gt;yet&lt;/em&gt; — that's the gap this pattern closes, not an indictment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Steal the pattern
&lt;/h2&gt;

&lt;p&gt;You don't need &lt;code&gt;mcp-worse&lt;/code&gt; specifically. You need the shape:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write down every claim your server's docs make about its wire behavior (cache hints, ordering, transport headers — whatever you promise).&lt;/li&gt;
&lt;li&gt;For each claim, ask: what's the smallest change that would make it false?&lt;/li&gt;
&lt;li&gt;Build &lt;em&gt;that&lt;/em&gt; — deliberately, once, labeled as a teaching/test fixture, never shipped as a product.&lt;/li&gt;
&lt;li&gt;Write one probe that checks both your real server and the broken companion, and asserts they land on opposite sides of every claim.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you can't build the broken version, you don't know what your claim depends on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Repo (full source referenced above): &lt;a href="https://github.com/Wolfe-Jam/mcp-better" rel="noopener noreferrer"&gt;github.com/Wolfe-Jam/mcp-better&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Prior in this thread: &lt;a href="https://dev.to/wolfejam/not-all-mcp-servers-are-equal-what-728-just-made-official-2f29"&gt;"Not all MCP servers are equal — what 7/28 just made official"&lt;/a&gt; — the claim=wire checklist this post makes concrete&lt;/li&gt;
&lt;li&gt;MCP spec: &lt;a href="https://modelcontextprotocol.io/specification/2026-07-28" rel="noopener noreferrer"&gt;modelcontextprotocol.io/specification/2026-07-28&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Close
&lt;/h2&gt;

&lt;p&gt;A README can't lie to a test that spawns the real process and reads the real wire. &lt;code&gt;mcp-worse&lt;/code&gt; isn't clever — two constants and a missing function call are enough. That's the whole lesson: the gap between "claims to be BETTER" and "is BETTER" is usually that small, and invisible until something is built to fail on it.&lt;/p&gt;

&lt;p&gt;Claim = wire. Build the broken version. Ship the probe that fails on it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's the smallest claim your own server makes that you've never tested?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I'm an AAIF Ambassador. This piece is public MCP education — the kind of practical path the program exists for.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>rust</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Not all MCP servers are equal — what 7/28 just made official</title>
      <dc:creator>wolfejam.dev</dc:creator>
      <pubDate>Fri, 31 Jul 2026 06:34:03 +0000</pubDate>
      <link>https://dev.to/wolfejam/not-all-mcp-servers-are-equal-what-728-just-made-official-2f29</link>
      <guid>https://dev.to/wolfejam/not-all-mcp-servers-are-equal-what-728-just-made-official-2f29</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — &lt;strong&gt;7/28&lt;/strong&gt; (wire: &lt;strong&gt;&lt;code&gt;2026-07-28&lt;/code&gt;&lt;/strong&gt;) is the modern MCP release. The big deal is &lt;strong&gt;STATELESS&lt;/strong&gt;: no protocol session, request/response core, Discover, stamped lists, Streamable HTTP routing headers. A server can expose &lt;code&gt;health&lt;/code&gt; on three hosts and still be &lt;strong&gt;three different machines operationally&lt;/strong&gt;. This post is the GOOD → BETTER map, plus a &lt;strong&gt;runnable textbook&lt;/strong&gt; you can install in minutes.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;MCP lives under the &lt;a href="https://aaif.io" rel="noopener noreferrer"&gt;Agentic AI Foundation&lt;/a&gt; / Linux Foundation. Spec: &lt;a href="https://modelcontextprotocol.io/specification/2026-07-28" rel="noopener noreferrer"&gt;modelcontextprotocol.io/specification/2026-07-28&lt;/a&gt;. Claude-side rollout note: &lt;a href="https://claude.com/blog/bringing-mcp-2026-07-28-to-claude" rel="noopener noreferrer"&gt;Bringing MCP 2026-07-28 to Claude&lt;/a&gt; (2026-07-28). Ship note: &lt;a href="https://faf.one/blog/mcp-better" rel="noopener noreferrer"&gt;faf.one/blog/mcp-better&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Same name, different machine
&lt;/h2&gt;

&lt;p&gt;InterOp is not “does &lt;code&gt;tools/list&lt;/code&gt; return a string called &lt;code&gt;echo&lt;/code&gt;?”&lt;/p&gt;

&lt;p&gt;InterOp is: &lt;strong&gt;does this server behave like a 7/28 peer under a modern client?&lt;/strong&gt;&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;Why it bites&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Does the client use &lt;strong&gt;Discover&lt;/strong&gt;, or only legacy &lt;code&gt;initialize&lt;/code&gt;?&lt;/td&gt;
&lt;td&gt;Handshake-as-identity is &lt;strong&gt;GOOD-era&lt;/strong&gt;. 7/28 is request/response + &lt;code&gt;server/discover&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Does &lt;code&gt;tools/list&lt;/code&gt; return &lt;strong&gt;&lt;code&gt;ttlMs&lt;/code&gt; + &lt;code&gt;cacheScope&lt;/code&gt;&lt;/strong&gt;?&lt;/td&gt;
&lt;td&gt;List cache is part of the modern contract — not optional polish for BETTER claims.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Is tool order &lt;strong&gt;stable&lt;/strong&gt;?&lt;/td&gt;
&lt;td&gt;Prompt-cache and client caching assume determinism.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On HTTP: are &lt;strong&gt;&lt;code&gt;Mcp-Method&lt;/code&gt; / &lt;code&gt;Mcp-Name&lt;/code&gt;&lt;/strong&gt; present?&lt;/td&gt;
&lt;td&gt;Routing without body parse — required on Streamable HTTP POSTs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Does the server invent &lt;strong&gt;session stickiness&lt;/strong&gt; as identity?&lt;/td&gt;
&lt;td&gt;Protocol sessions are gone. App state = explicit handles if anything.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If two servers both advertise &lt;code&gt;echo&lt;/code&gt; but only one answers those questions honestly, they are &lt;strong&gt;not&lt;/strong&gt; interchangeable. Host A “works.” Host B “flakes.” The tool name stayed the same. The &lt;strong&gt;operational surface&lt;/strong&gt; did not.&lt;/p&gt;




&lt;h2&gt;
  
  
  GOOD habits (pre-7/28 muscle memory)
&lt;/h2&gt;

&lt;p&gt;None of this is “you’re bad.” It was the world the old samples taught.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Habit&lt;/th&gt;
&lt;th&gt;What it looked like&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Session as identity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sticky &lt;code&gt;Mcp-Session-Id&lt;/code&gt;, “connected” means long-lived peer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Handshake as the event&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;initialize&lt;/code&gt; / &lt;code&gt;initialized&lt;/code&gt; as the main lifecycle story&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Unstamped lists&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;tools/list&lt;/code&gt; works, but no cache hints — clients re-poll forever&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Host-lucky InterOp&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Works in one Desktop config; mystery failure in another&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Banner claims&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;README says “modern MCP”; wire is still 2024-shaped&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Call that &lt;strong&gt;GOOD&lt;/strong&gt;: real MCP, real tools, real value — with &lt;strong&gt;session-era operational assumptions&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  BETTER after 7/28 (checklist you can audit)
&lt;/h2&gt;

&lt;p&gt;Humans say &lt;strong&gt;7/28&lt;/strong&gt;. Machines negotiate &lt;strong&gt;&lt;code&gt;2026-07-28&lt;/code&gt;&lt;/strong&gt;. The big deal is &lt;strong&gt;STATELESS&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;BETTER check&lt;/th&gt;
&lt;th&gt;Spec-shaped meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Stateless core&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No protocol session; any request can hit any healthy instance (HTTP)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Discover&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Clients prefer Discover / Auto → 7/28; servers answer discover correctly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Self-describing traffic&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Version + capabilities travel with the request (&lt;code&gt;_meta&lt;/code&gt; / headers)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Stamped lists&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Positive &lt;code&gt;ttlMs&lt;/code&gt;, intentional &lt;code&gt;cacheScope&lt;/code&gt; (&lt;code&gt;public&lt;/code&gt; for static catalogs)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Stable tool order&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Same process, same order across N list calls&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;HTTP road (if claimed)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Streamable HTTP + &lt;code&gt;Mcp-Method&lt;/code&gt; / &lt;code&gt;Mcp-Name&lt;/code&gt;; no fake “we’re remote-prod” without auth&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Honest claim surface&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Docs + CI prove what you claim; no deprecated Roots/Sampling/protocol Logging as greenfield features&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MRTR / Tasks / OAuth&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Real, but &lt;strong&gt;not&lt;/strong&gt; required to be a BETTER &lt;em&gt;tools&lt;/em&gt; textbook on day one — document later if you go deep&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;BETTER is not “more tools.”&lt;/strong&gt; BETTER is &lt;strong&gt;claim = wire&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  If MCP had an AGENTS.md
&lt;/h2&gt;

&lt;p&gt;One box. Steal it for every server README you touch:&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="gu"&gt;## Operational contract (7/28)&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Protocol: 2026-07-28
&lt;span class="p"&gt;-&lt;/span&gt; Lifecycle: Discover-compatible (not initialize-only)
&lt;span class="p"&gt;-&lt;/span&gt; tools/list: ttlMs + cacheScope + stable order
&lt;span class="p"&gt;-&lt;/span&gt; Transports: stdio (default) · Streamable HTTP (if enabled — document bind + auth posture)
&lt;span class="p"&gt;-&lt;/span&gt; Non-goals: (resources / OAuth / Tasks — say so if out of scope)
&lt;span class="p"&gt;-&lt;/span&gt; Prove it: (link CI / smoke that fails when you lie)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Short. Specific. Actionable. Same discipline as a good AGENTS.md — &lt;strong&gt;facts the peer can verify&lt;/strong&gt;, not vibes.&lt;/p&gt;




&lt;h2&gt;
  
  
  10-minute textbook: &lt;code&gt;mcp-better&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/Wolfe-Jam/mcp-better" rel="noopener noreferrer"&gt;&lt;code&gt;mcp-better&lt;/code&gt;&lt;/a&gt; is a &lt;strong&gt;small&lt;/strong&gt; Rust server built for this era — not a product platform. Two tools: &lt;code&gt;health&lt;/code&gt;, &lt;code&gt;echo&lt;/code&gt;. Official &lt;code&gt;rmcp&lt;/code&gt; 3. Discover-compatible. Stamped list. v0.1 = 7/28 over &lt;strong&gt;stdio&lt;/strong&gt;. v0.2 = same era + opt-in &lt;strong&gt;Streamable HTTP&lt;/strong&gt; (local demo).&lt;/p&gt;

&lt;p&gt;On crates.io and the MCP Registry as &lt;code&gt;io.github.Wolfe-Jam/mcp-better&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1 — Install
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo &lt;span class="nb"&gt;install &lt;/span&gt;mcp-better &lt;span class="nt"&gt;--version&lt;/span&gt; 0.2.0
mcp-better &lt;span class="nt"&gt;--help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First install compiles Rust deps once — one-time wait; then you’re done.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 — Run stdio (default)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mcp-better
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Point a Discover-capable client / smoke at it. Or from the repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Wolfe-Jam/mcp-better.git
&lt;span class="nb"&gt;cd &lt;/span&gt;mcp-better
cargo build &lt;span class="nt"&gt;--bins&lt;/span&gt;
cargo run &lt;span class="nt"&gt;--example&lt;/span&gt; stdio-client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expect: negotiated &lt;strong&gt;&lt;code&gt;2026-07-28&lt;/code&gt;&lt;/strong&gt;, list stamps (&lt;code&gt;ttlMs&lt;/code&gt; / &lt;code&gt;cacheScope&lt;/code&gt;), tools in stable order: &lt;code&gt;health&lt;/code&gt;, then &lt;code&gt;echo&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 — Optional HTTP road (same era)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mcp-better &lt;span class="nt"&gt;--http&lt;/span&gt;
&lt;span class="c"&gt;# http://127.0.0.1:8787/mcp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Local demo only — no auth/TLS (see the repo SECURITY.md). From source, &lt;code&gt;http-smoke&lt;/code&gt; exercises list + health + echo with &lt;strong&gt;&lt;code&gt;Mcp-Method&lt;/code&gt; / &lt;code&gt;Mcp-Name&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4 — What you just proved
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Claim&lt;/th&gt;
&lt;th&gt;Evidence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Built for 7/28&lt;/td&gt;
&lt;td&gt;Discover path + era string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;List cache&lt;/td&gt;
&lt;td&gt;Positive &lt;code&gt;ttlMs&lt;/code&gt;, &lt;code&gt;public&lt;/code&gt; scope for a static catalog&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dual transport&lt;/td&gt;
&lt;td&gt;stdio default · HTTP opt-in — &lt;strong&gt;not&lt;/strong&gt; a second protocol “version”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Small surface&lt;/td&gt;
&lt;td&gt;Two tools — enough to teach the contract&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That’s the point of a textbook: &lt;strong&gt;copy the operational contract&lt;/strong&gt;, not a 40-tool megaserver.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this is not
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Not a tour of every SEP.
&lt;/li&gt;
&lt;li&gt;Not “everyone has migrated.” Hosts and SDKs roll out on their clocks (Claude products are rolling 7/28 support — see Anthropic’s post).
&lt;/li&gt;
&lt;li&gt;Not a connector-directory pitch. Open Registry + honest wire still matter when app-store shelves optimize for other shapes.
&lt;/li&gt;
&lt;li&gt;Not a product install funnel. One runnable example. One checklist.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Further reading (one hop)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Spec: &lt;a href="https://modelcontextprotocol.io/specification/2026-07-28" rel="noopener noreferrer"&gt;MCP 2026-07-28&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Changelog: &lt;a href="https://modelcontextprotocol.io/specification/2026-07-28/changelog" rel="noopener noreferrer"&gt;Key changes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Textbook: &lt;a href="https://github.com/Wolfe-Jam/mcp-better" rel="noopener noreferrer"&gt;github.com/Wolfe-Jam/mcp-better&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Ship note: &lt;a href="https://faf.one/blog/mcp-better" rel="noopener noreferrer"&gt;faf.one/blog/mcp-better&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Close
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;7/28 is hot for a reason.&lt;/strong&gt; The big deal is &lt;strong&gt;STATELESS&lt;/strong&gt; — Discover, stamped lists, and honest HTTP headers are how the wire enforces that.  &lt;/p&gt;

&lt;p&gt;If you only remember one line: &lt;strong&gt;same tool name ≠ same operational server.&lt;/strong&gt; Audit the contract. Ship the smoke. Prefer BETTER over banner.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I’m an AAIF Ambassador. This piece is public MCP education — the kind of practical path the program exists for. Questions welcome.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>AGENTS.md Series Wrap: Five Lessons, FAQs, and What I'd Do Differently</title>
      <dc:creator>wolfejam.dev</dc:creator>
      <pubDate>Tue, 28 Jul 2026 14:48:53 +0000</pubDate>
      <link>https://dev.to/wolfejam/agentsmd-series-wrap-five-lessons-faqs-and-what-id-do-differently-55cm</link>
      <guid>https://dev.to/wolfejam/agentsmd-series-wrap-five-lessons-faqs-and-what-id-do-differently-55cm</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — Five posts. One file. The whole arc: &lt;strong&gt;what it is → write one → why it rots → keep it true → author from facts.&lt;/strong&gt; This is the wrap: the map of the series, five FAQs, and the lesson that stuck — &lt;strong&gt;facts block vs unique prose.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📚 The AGENTS.md Series&lt;/strong&gt; · you're on &lt;strong&gt;Part 6 — Series wrap&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://dev.to/wolfejam/agentsmd-the-one-file-that-makes-ai-coding-agents-actually-useful-ckj"&gt;I · field guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/wolfejam/agentsmd-hands-on-build-one-step-by-step-and-watch-an-agent-use-it-3g27"&gt;II · hands-on&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/wolfejam/your-agentsmd-is-already-stale-and-your-agent-trusts-it-completely-2nfh"&gt;III · stale&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/wolfejam/agentsmd-kept-true-stop-the-rot-step-by-step-and-watch-your-agent-trust-it-3mjb"&gt;IV · keep it true&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/wolfejam/agentsmd-from-facts-author-one-that-cant-drift-step-by-step-37fn"&gt;V · from facts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;VI · series wrap — you're here&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;New here?&lt;/em&gt; &lt;a href="https://agents.md" rel="noopener noreferrer"&gt;AGENTS.md&lt;/a&gt; is a single Markdown file at the repo root that tells AI coding agents how to work in it — commands, tests, conventions, guardrails. Open standard under the AAIF / Linux Foundation. Start at &lt;a href="https://dev.to/wolfejam/agentsmd-the-one-file-that-makes-ai-coding-agents-actually-useful-ckj"&gt;Part I&lt;/a&gt; if you want the full path.&lt;/p&gt;




&lt;h2&gt;
  
  
  What we covered
&lt;/h2&gt;

&lt;p&gt;Five lessons. One job: make the briefing agents obey &lt;strong&gt;true&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;One line&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://dev.to/wolfejam/agentsmd-the-one-file-that-makes-ai-coding-agents-actually-useful-ckj"&gt;I&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Field guide&lt;/td&gt;
&lt;td&gt;What belongs in AGENTS.md — and what doesn't&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://dev.to/wolfejam/agentsmd-hands-on-build-one-step-by-step-and-watch-an-agent-use-it-3g27"&gt;II&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hands-on&lt;/td&gt;
&lt;td&gt;Build a real file, then point an agent at it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://dev.to/wolfejam/your-agentsmd-is-already-stale-and-your-agent-trusts-it-completely-2nfh"&gt;III&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The threat&lt;/td&gt;
&lt;td&gt;A stale file is still obeyed — at full confidence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://dev.to/wolfejam/agentsmd-kept-true-stop-the-rot-step-by-step-and-watch-your-agent-trust-it-3mjb"&gt;IV&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Discipline&lt;/td&gt;
&lt;td&gt;Same-PR anti-rot, verify with an agent, keep truth current&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://dev.to/wolfejam/agentsmd-from-facts-author-one-that-cant-drift-step-by-step-37fn"&gt;V&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Endgame&lt;/td&gt;
&lt;td&gt;Author from repo facts so the file can't drift&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you only read one besides this wrap: &lt;strong&gt;III&lt;/strong&gt; if you've never felt rot; &lt;strong&gt;V&lt;/strong&gt; if you already have a file and want it to stop lying.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step-through: how the arc fits
&lt;/h2&gt;

&lt;p&gt;Read it as a single path, not five islands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I   What is AGENTS.md?          →  principles, shape, anti-patterns
II  Write one                   →  real file, real agent payoff
III Watch it go stale           →  why "present" ≠ "true"
IV  Keep it true by hand        →  human discipline while the code moves
V   Stop hand-maintaining facts →  regenerate what the tree already knows
VI  This post                   →  map · FAQs · the lesson that remains
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;I and II&lt;/strong&gt; get you a file that works.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;III and IV&lt;/strong&gt; get you a file that &lt;em&gt;stays&lt;/em&gt; honest under change.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;V&lt;/strong&gt; removes the class of lines that should never have been typed by hand.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;VI&lt;/strong&gt; (here) is the map and the judgment call: what still needs a human.&lt;/p&gt;

&lt;p&gt;The series does &lt;strong&gt;not&lt;/strong&gt; argue for a longer AGENTS.md. It argues for a &lt;strong&gt;truer&lt;/strong&gt; one: a concise &lt;strong&gt;facts block&lt;/strong&gt;, then &lt;strong&gt;your prose&lt;/strong&gt;. Overall length is yours.&lt;/p&gt;




&lt;h2&gt;
  
  
  Five FAQs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Do I need AGENTS.md if I already have README / CONTRIBUTING / CLAUDE.md?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Yes, if agents work in the repo.&lt;/strong&gt; README is for humans browsing. CONTRIBUTING is process. Vendor files (&lt;code&gt;CLAUDE.md&lt;/code&gt;, etc.) are tool-specific. AGENTS.md is the &lt;strong&gt;tool-agnostic briefing&lt;/strong&gt; many agents already look for. Keep it short; link out for depth. Don't duplicate a novel.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. How long should it be?
&lt;/h3&gt;

&lt;p&gt;Short enough that an agent (and a human) will still trust every line. Prefer &lt;strong&gt;commands you can run&lt;/strong&gt;, &lt;strong&gt;paths that exist&lt;/strong&gt;, and &lt;strong&gt;guardrails that bite&lt;/strong&gt;. If a section can't be verified against the tree or a one-line rule, cut it or move it.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. What's the #1 failure mode?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Stale truth that still looks official.&lt;/strong&gt; Renamed scripts, dead test commands, structure maps that lag the tree — the agent obeys them at full confidence. Part III is the whole point. Freshness is not a nicety; it's the product.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Should every line be auto-generated?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;No.&lt;/strong&gt; Generate (or check) what the &lt;strong&gt;repo can prove&lt;/strong&gt;: package names, scripts, layout, CI commands. &lt;strong&gt;Keep human&lt;/strong&gt; what only a human owns: judgment, product "why," team norms that aren't in the tree, "never do X" that isn't a linter rule yet. Part V is the endgame for the &lt;em&gt;facts&lt;/em&gt; half — not a replacement for judgment.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Where do I start if I have nothing?
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://dev.to/wolfejam/agentsmd-the-one-file-that-makes-ai-coding-agents-actually-useful-ckj"&gt;Part I&lt;/a&gt; — shape and anti-patterns
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/wolfejam/agentsmd-hands-on-build-one-step-by-step-and-watch-an-agent-use-it-3g27"&gt;Part II&lt;/a&gt; — write one and verify with an agent
&lt;/li&gt;
&lt;li&gt;When it starts lying, &lt;a href="https://dev.to/wolfejam/agentsmd-kept-true-stop-the-rot-step-by-step-and-watch-your-agent-trust-it-3mjb"&gt;IV&lt;/a&gt; then &lt;a href="https://dev.to/wolfejam/agentsmd-from-facts-author-one-that-cant-drift-step-by-step-37fn"&gt;V&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Don't skip III forever — you'll meet it the hard way.&lt;/p&gt;




&lt;h2&gt;
  
  
  My lesson: facts block vs unique prose
&lt;/h2&gt;

&lt;p&gt;If the series leaves one idea standing, make it this.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Facts block&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Unique prose&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;What&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Things the repo already knows or can prove&lt;/td&gt;
&lt;td&gt;Things only a human should assert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Examples&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Build/test commands, package layout, entrypoints, CI checks&lt;/td&gt;
&lt;td&gt;Why the product exists, team taste, soft norms, hard "never" that isn't encoded yet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Failure mode&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Drift (tree moves, file doesn't)&lt;/td&gt;
&lt;td&gt;Vagueness or lecture that no agent can act on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fix&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Author/check from the tree; fail CI on drift (&lt;a href="https://dev.to/wolfejam/agentsmd-from-facts-author-one-that-cant-drift-step-by-step-37fn"&gt;V&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;Write short, actionable, owned by a human; same PR as the change when norms move (&lt;a href="https://dev.to/wolfejam/agentsmd-kept-true-stop-the-rot-step-by-step-and-watch-your-agent-trust-it-3mjb"&gt;IV&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;BETTER AGENTS.md&lt;/strong&gt; = facts that match the tree &lt;strong&gt;plus&lt;/strong&gt; prose that earns its tokens.&lt;/p&gt;

&lt;p&gt;I used to treat the whole file as "documentation I should maintain." The series taught me to &lt;strong&gt;split the job&lt;/strong&gt;: automate honesty for the map; reserve human attention for judgment. That's the difference between a file that &lt;em&gt;exists&lt;/em&gt; and a file that &lt;em&gt;deserves to be trusted&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What "good" vs "BETTER" looks like in practice
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Good&lt;/th&gt;
&lt;th&gt;BETTER&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;File exists at repo root&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent can follow setup/test&lt;/td&gt;
&lt;td&gt;Often&lt;/td&gt;
&lt;td&gt;Commands match &lt;code&gt;package.json&lt;/code&gt; / &lt;code&gt;just&lt;/code&gt; / CI &lt;strong&gt;today&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Structure / map&lt;/td&gt;
&lt;td&gt;Hand-wavy or partial&lt;/td&gt;
&lt;td&gt;Matches live tree (or deliberately points at it)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stale lines&lt;/td&gt;
&lt;td&gt;"We'll fix later"&lt;/td&gt;
&lt;td&gt;Same PR as the change, or regenerate facts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Length&lt;/td&gt;
&lt;td&gt;Long and impressive&lt;/td&gt;
&lt;td&gt;Short and true&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Goose, Cursor, Claude Code, Copilot, Codex — none of them need your autobiography. They need a briefing that won't send them into a renamed script at full confidence.&lt;/p&gt;




&lt;h2&gt;
  
  
  Q&amp;amp;A / feedback
&lt;/h2&gt;

&lt;p&gt;This wraps the series for the AAIF Ambassador program — A practical, vendor-free AGENTS.md guide.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I'd like to hear from you:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Where does &lt;em&gt;your&lt;/em&gt; AGENTS.md rot first (commands, structure, guardrails, something else)?
&lt;/li&gt;
&lt;li&gt;Did you try authoring from facts (Part V) — what broke?
&lt;/li&gt;
&lt;li&gt;What's still missing from the open &lt;a href="https://agents.md" rel="noopener noreferrer"&gt;AGENTS.md&lt;/a&gt; conversation that these five parts didn't touch?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Comment on this post, or open a discussion on a project you care about (for example: keep &lt;em&gt;that&lt;/em&gt; repo's Structure true to &lt;code&gt;crates/&lt;/code&gt; / &lt;code&gt;src/&lt;/code&gt;). The standard gets better when real trees push back on pretty docs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Series index (bookmark this)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://dev.to/wolfejam/agentsmd-the-one-file-that-makes-ai-coding-agents-actually-useful-ckj"&gt;AGENTS.md: The One File That Makes AI Coding Agents Actually Useful&lt;/a&gt; — field guide
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/wolfejam/agentsmd-hands-on-build-one-step-by-step-and-watch-an-agent-use-it-3g27"&gt;AGENTS.md, Hands-On: Build One Step by Step&lt;/a&gt; — tutorial
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/wolfejam/your-agentsmd-is-already-stale-and-your-agent-trusts-it-completely-2nfh"&gt;Your AGENTS.md Is Already Stale (And Your Agent Trusts It Completely)&lt;/a&gt; — the threat
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/wolfejam/agentsmd-kept-true-stop-the-rot-step-by-step-and-watch-your-agent-trust-it-3mjb"&gt;AGENTS.md Kept True: Stop the Rot Step by Step&lt;/a&gt; — discipline
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/wolfejam/agentsmd-from-facts-author-one-that-cant-drift-step-by-step-37fn"&gt;AGENTS.md From Facts: Author One That Can't Drift&lt;/a&gt; — endgame tool path
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;This post&lt;/strong&gt; — wrap · FAQs · facts vs prose
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Further reading:&lt;/strong&gt; the standard itself at &lt;a href="https://agents.md" rel="noopener noreferrer"&gt;agents.md&lt;/a&gt;, and a section-by-section field guide — what earns a line, ordering, length, the anti-patterns — at &lt;a href="https://faf.one/agents" rel="noopener noreferrer"&gt;faf.one/agents&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Thanks for reading the series. &lt;br&gt;
Ship a short true file. &lt;br&gt;
Then keep it true.&lt;/em&gt; &lt;br&gt;
👍&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>productivity</category>
      <category>devtools</category>
    </item>
    <item>
      <title>AGENTS.md From Facts: Author One That Can't Drift (Step by Step)</title>
      <dc:creator>wolfejam.dev</dc:creator>
      <pubDate>Fri, 24 Jul 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/wolfejam/agentsmd-from-facts-author-one-that-cant-drift-step-by-step-37fn</link>
      <guid>https://dev.to/wolfejam/agentsmd-from-facts-author-one-that-cant-drift-step-by-step-37fn</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — Parts I–IV got you a real AGENTS.md and the discipline to keep it honest. This is the endgame Part IV pointed at: &lt;strong&gt;stop hand-maintaining lines the repo already knows.&lt;/strong&gt; Author a minimal file from detected facts, protect human notes outside markers, fail CI when it drifts, point an agent at it. Six steps. One CLI. Facts only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Series:&lt;/strong&gt; &lt;a href="https://dev.to/wolfejam/agentsmd-the-one-file-that-makes-ai-coding-agents-actually-useful-ckj"&gt;I · field guide&lt;/a&gt; → &lt;a href="https://dev.to/wolfejam/agentsmd-hands-on-build-one-step-by-step-and-watch-an-agent-use-it-3g27"&gt;II · hands-on&lt;/a&gt; → &lt;a href="https://dev.to/wolfejam/your-agentsmd-is-already-stale-and-your-agent-trusts-it-completely-2nfh"&gt;III · stale&lt;/a&gt; → &lt;a href="https://dev.to/wolfejam/agentsmd-kept-true-stop-the-rot-step-by-step-and-watch-your-agent-trust-it-3mjb"&gt;IV · keep it true&lt;/a&gt; → &lt;strong&gt;you're on V&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;New here?&lt;/em&gt; &lt;a href="https://agents.md" rel="noopener noreferrer"&gt;AGENTS.md&lt;/a&gt; is a single Markdown file at the repo root that tells AI coding agents how to work in it — commands, tests, conventions, guardrails. Open standard under the AAIF / Linux Foundation. If you haven't felt the payoff (or the rot), start at &lt;a href="https://dev.to/wolfejam/agentsmd-the-one-file-that-makes-ai-coding-agents-actually-useful-ckj"&gt;Part I&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why the file still earns its keep
&lt;/h2&gt;

&lt;p&gt;Twenty seconds on &lt;strong&gt;why AGENTS.md exists&lt;/strong&gt; before we automate it:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Benefit&lt;/th&gt;
&lt;th&gt;What the agent gets&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Faster first move&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Real setup / build / test commands — no guessing the runner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Self-check loop&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tests as the bar for "done" — re-derive truth from the world&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Less interruption&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Map + conventions — fewer "where do routes go?" questions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fewer expensive mistakes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Guardrails (always / ask first / never) + definition of done&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;One briefing, many tools&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tool-agnostic Markdown — Cursor, Claude Code, Codex, Copilot, goose, Grok, peers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Short. Current. Specific. Actionable. Parts &lt;a href="https://dev.to/wolfejam/agentsmd-the-one-file-that-makes-ai-coding-agents-actually-useful-ckj"&gt;I&lt;/a&gt;–&lt;a href="https://dev.to/wolfejam/agentsmd-hands-on-build-one-step-by-step-and-watch-an-agent-use-it-3g27"&gt;II&lt;/a&gt; covered &lt;em&gt;what&lt;/em&gt; and &lt;em&gt;how to write it&lt;/em&gt;. Parts &lt;a href="https://dev.to/wolfejam/your-agentsmd-is-already-stale-and-your-agent-trusts-it-completely-2nfh"&gt;III&lt;/a&gt;–&lt;a href="https://dev.to/wolfejam/agentsmd-kept-true-stop-the-rot-step-by-step-and-watch-your-agent-trust-it-3mjb"&gt;IV&lt;/a&gt; covered &lt;em&gt;why it dies&lt;/em&gt; and &lt;em&gt;the human discipline that keeps it alive&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Part IV Step 5's honest endgame: &lt;strong&gt;regenerate from the repo so the file can't drift.&lt;/strong&gt; This is that tutorial.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In one line:&lt;/strong&gt; Part II taught you to &lt;strong&gt;write&lt;/strong&gt; the file · Part IV taught you to &lt;strong&gt;keep&lt;/strong&gt; it true · &lt;strong&gt;Part V hands you the tool&lt;/strong&gt; — stop hand-maintaining what the tree already knows. Ready?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Part II: &lt;a href="https://dev.to/wolfejam/agentsmd-hands-on-build-one-step-by-step-and-watch-an-agent-use-it-3g27"&gt;https://dev.to/wolfejam/agentsmd-hands-on-build-one-step-by-step-and-watch-an-agent-use-it-3g27&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part IV: &lt;a href="https://dev.to/wolfejam/agentsmd-kept-true-stop-the-rot-step-by-step-and-watch-your-agent-trust-it-3mjb"&gt;https://dev.to/wolfejam/agentsmd-kept-true-stop-the-rot-step-by-step-and-watch-your-agent-trust-it-3mjb&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The trap
&lt;/h2&gt;

&lt;p&gt;Two failure modes, both real:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Hand-written and left alone → it rots.&lt;/strong&gt; You rename a script; the agent still runs the old one at full confidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Let an AI write the whole file" → it bloats.&lt;/strong&gt; Research (&lt;a href="https://arxiv.org/abs/2602.11988" rel="noopener noreferrer"&gt;Gloaguen et al., 2026&lt;/a&gt;) found LLM-written instruction files can &lt;em&gt;reduce&lt;/em&gt; task success and raise cost — plausible padding the agent over-obeys.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The fix is not more vibes. &lt;strong&gt;Facts only:&lt;/strong&gt; every managed line traces to something the tree already declares — real scripts, real configs, real entry points. Nothing invented. Nothing padded.&lt;/p&gt;

&lt;p&gt;That's &lt;a href="https://github.com/Wolfe-Jam/agents-md-facts" rel="noopener noreferrer"&gt;&lt;code&gt;agents-md-facts&lt;/code&gt;&lt;/a&gt;: a small CLI that &lt;strong&gt;authors&lt;/strong&gt; a minimal AGENTS.md from repo facts. Open a real project and follow along.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1 — Dry-run first
&lt;/h2&gt;

&lt;p&gt;Any repo with a real &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;pyproject.toml&lt;/code&gt;, &lt;code&gt;Cargo.toml&lt;/code&gt;, or &lt;code&gt;go.mod&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx agents-md-facts &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get the plan — orientation, setup/build, tests, map, conventions, guardrails, definition of done — &lt;strong&gt;without writing a file&lt;/strong&gt;. Sanity-check: are those the commands &lt;em&gt;you&lt;/em&gt; actually run? If not, fix the scripts/config (source of truth), not the prose later.&lt;/p&gt;

&lt;p&gt;Optional — managed block only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx agents-md-facts &lt;span class="nt"&gt;--stdout&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2 — Author (or refresh)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx agents-md-facts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Writes or refreshes &lt;code&gt;AGENTS.md&lt;/code&gt; at the repo root. Managed region:&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="c"&gt;&amp;lt;!-- agents:from-facts:start --&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- authored by agents-md-facts — from your repo's facts, never guessed · re-run to refresh --&amp;gt;&lt;/span&gt;

&lt;span class="gh"&gt;# AGENTS.md — your-project&lt;/span&gt;

…

&lt;span class="c"&gt;&amp;lt;!-- agents:from-facts:end --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Non-destructive:&lt;/strong&gt; everything &lt;strong&gt;outside&lt;/strong&gt; those markers is yours. Re-run anytime; hand-written notes survive. Put tool quirks, domain gotchas, and "don't touch this without a human" &lt;em&gt;outside&lt;/em&gt; the block. Author the rest from facts.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3 — Read it like an agent
&lt;/h2&gt;

&lt;p&gt;Open the file. Shape for a small Node/TS example (exact style from the tool's &lt;a href="https://github.com/Wolfe-Jam/agents-md-facts/tree/main/examples" rel="noopener noreferrer"&gt;examples&lt;/a&gt;):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Orientation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;TypeScript · Node.js · npm package manager · v1.0.0&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup &amp;amp; build&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;    &lt;span class="c"&gt;# install dependencies&lt;/span&gt;
npm run build    &lt;span class="c"&gt;# build&lt;/span&gt;
npm run dev    &lt;span class="c"&gt;# dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Run the tests&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run &lt;span class="nb"&gt;test
&lt;/span&gt;npm run lint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Where things live&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;package.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/index.ts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tsconfig.json&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Conventions&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TypeScript strict mode (&lt;code&gt;tsconfig.json&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;ESM modules (&lt;code&gt;type: module&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Style enforced by ESLint · Prettier — obey the configs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Guardrails&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Always OK:&lt;/strong&gt; read files, run the tests (&lt;code&gt;npm run test&lt;/code&gt;), build the project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask first:&lt;/strong&gt; dependency installs, deletions, migrations / schema changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never:&lt;/strong&gt; force-push, commit secrets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Definition of Done&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Done when: &lt;code&gt;npm run lint&lt;/code&gt; exits 0 · &lt;code&gt;npm run test&lt;/code&gt; passes · committed with a clear message.&lt;/p&gt;

&lt;p&gt;Part IV rules, automated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Commands&lt;/strong&gt; copy-pasteable and backticked as literals&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tests&lt;/strong&gt; where the agent needs a feedback loop&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conventions&lt;/strong&gt; point at configs — don't restate "use 2 spaces"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guardrails&lt;/strong&gt; enable-first (always / ask / never)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Definition of Done&lt;/strong&gt; mechanically checkable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a line wouldn't resolve an ambiguity for a fresh agent, the tool drops it. Short by construction.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4 — Human layer (outside the markers)
&lt;/h2&gt;

&lt;p&gt;Facts can't know product judgment. Below the end marker (or above the start), add what only &lt;em&gt;you&lt;/em&gt; know:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!-- agents:from-facts:end --&amp;gt;

## Project notes (human)

- Prefer the query builder in `src/db/` — no raw SQL in handlers.
- Feature flags live in `src/flags.ts`; don't hardcode rollout in routes.
- Billing code is off-limits without a human in the loop.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next refresh leaves this intact. &lt;strong&gt;Machine-owned facts inside · human-owned judgment outside.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5 — Make drift a build failure
&lt;/h2&gt;

&lt;p&gt;Part IV's anti-rot rule: AGENTS.md changes in the &lt;strong&gt;same PR&lt;/strong&gt; as the code. Automate it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Local / CI:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx agents-md-facts &lt;span class="nt"&gt;--check&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exits &lt;code&gt;1&lt;/code&gt; if missing or the managed block is stale vs the repo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub Action:&lt;/strong&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="c1"&gt;# .github/workflows/agents-md.yml&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;AGENTS.md&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&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;check&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;Wolfe-Jam/agents-md-facts@v0.1.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pre-commit:&lt;/strong&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="c1"&gt;# .pre-commit-config.yaml&lt;/span&gt;
&lt;span class="na"&gt;repos&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/Wolfe-Jam/agents-md-facts&lt;/span&gt;
    &lt;span class="na"&gt;rev&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v0.1.0&lt;/span&gt;
    &lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;agents-md-facts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then: &lt;code&gt;pre-commit install&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When scripts or layout change, fix in the &lt;strong&gt;same PR&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx agents-md-facts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Treat the file like code — because your agent already does.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6 — Verify with an agent
&lt;/h2&gt;

&lt;p&gt;Same proof as Parts II and IV. Small real task:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Add a tested &lt;code&gt;/health&lt;/code&gt; endpoint."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Watch for:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It reads &lt;code&gt;AGENTS.md&lt;/code&gt; first.&lt;/li&gt;
&lt;li&gt;It runs the &lt;strong&gt;exact&lt;/strong&gt; test/lint commands from the file.&lt;/li&gt;
&lt;li&gt;It respects Always / Ask / Never.&lt;/li&gt;
&lt;li&gt;It hits Definition of Done without you re-explaining the toolchain.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If it runs the wrong command or ignores a convention, &lt;strong&gt;the file (or the underlying scripts) lied&lt;/strong&gt; — fix the fact source, re-author, re-check. Don't scold the agent for trusting the briefing you gave it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this does &lt;em&gt;not&lt;/em&gt; replace
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Still yours&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Judgment outside markers&lt;/td&gt;
&lt;td&gt;Facts don't know billing, legal, or when to "ask the Owner first"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;README for humans&lt;/td&gt;
&lt;td&gt;AGENTS.md = how to &lt;em&gt;work&lt;/em&gt;; README = what/why&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code review&lt;/td&gt;
&lt;td&gt;A true file helps the agent; humans still ship&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lint of your prose&lt;/td&gt;
&lt;td&gt;Complements bloat-linters — this &lt;strong&gt;authors clean&lt;/strong&gt; from truth&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You can still hand-write the whole file (Part II). You can still run Part IV without a generator. The CLI is for lines that should never have been hand-copied from &lt;code&gt;package.json&lt;/code&gt; in the first place.&lt;/p&gt;




&lt;h2&gt;
  
  
  The loop, closed
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;What you learned&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dev.to/wolfejam/agentsmd-the-one-file-that-makes-ai-coding-agents-actually-useful-ckj"&gt;I&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;What belongs · anti-patterns · short &amp;gt; complete&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dev.to/wolfejam/agentsmd-hands-on-build-one-step-by-step-and-watch-an-agent-use-it-3g27"&gt;II&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Build section by section · watch an agent use it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dev.to/wolfejam/your-agentsmd-is-already-stale-and-your-agent-trusts-it-completely-2nfh"&gt;III&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Stale cache worse than empty · facts only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dev.to/wolfejam/agentsmd-kept-true-stop-the-rot-step-by-step-and-watch-your-agent-trust-it-3mjb"&gt;IV&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Same-PR updates · point-at-config · enable-first guardrails&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;V (this)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Author from facts · markers · &lt;code&gt;--check&lt;/code&gt; · CI · agent verify&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Short. Current. Specific. Actionable. And now &lt;strong&gt;re-derivable from the tree&lt;/strong&gt; — so the agent stops inheriting last sprint's lies.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where to go deeper
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Series:&lt;/strong&gt; &lt;a href="https://dev.to/wolfejam/agentsmd-the-one-file-that-makes-ai-coding-agents-actually-useful-ckj"&gt;I&lt;/a&gt; · &lt;a href="https://dev.to/wolfejam/agentsmd-hands-on-build-one-step-by-step-and-watch-an-agent-use-it-3g27"&gt;II&lt;/a&gt; · &lt;a href="https://dev.to/wolfejam/your-agentsmd-is-already-stale-and-your-agent-trusts-it-completely-2nfh"&gt;III&lt;/a&gt; · &lt;a href="https://dev.to/wolfejam/agentsmd-kept-true-stop-the-rot-step-by-step-and-watch-your-agent-trust-it-3mjb"&gt;IV&lt;/a&gt; · &lt;strong&gt;V (you are here)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tool (source + stars)&lt;/strong&gt; — &lt;a href="https://github.com/Wolfe-Jam/agents-md-facts" rel="noopener noreferrer"&gt;github.com/Wolfe-Jam/agents-md-facts&lt;/a&gt; · &lt;code&gt;npx agents-md-facts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Standard&lt;/strong&gt; — &lt;a href="https://agents.md" rel="noopener noreferrer"&gt;agents.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Field guide&lt;/strong&gt; (what earns a line, length, anti-patterns) — &lt;a href="https://faf.one/agents" rel="noopener noreferrer"&gt;faf.one/agents&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⭐ If this saved you a hand-written or LLM-bloated AGENTS.md, a &lt;a href="https://github.com/Wolfe-Jam/agents-md-facts" rel="noopener noreferrer"&gt;star on the repo&lt;/a&gt; helps others find it.&lt;/p&gt;

&lt;p&gt;Keep the facts honest. Your agent only ever trusts the last true thing you told it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>devtools</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>AGENTS.md, Kept True: Stop the Rot Step by Step (and Watch Your Agent Trust It)</title>
      <dc:creator>wolfejam.dev</dc:creator>
      <pubDate>Sat, 11 Jul 2026 17:56:44 +0000</pubDate>
      <link>https://dev.to/wolfejam/agentsmd-kept-true-stop-the-rot-step-by-step-and-watch-your-agent-trust-it-3mjb</link>
      <guid>https://dev.to/wolfejam/agentsmd-kept-true-stop-the-rot-step-by-step-and-watch-your-agent-trust-it-3mjb</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — A good AGENTS.md isn't written, it's &lt;em&gt;maintained&lt;/em&gt; — and the maintenance is where every one dies. Here's the exact discipline that keeps yours true: tie every line to a fact in the repo, verify it runs, and make it change in the same PR as the code. Six steps, one worked example, and an agent that stops guessing.&lt;/p&gt;

&lt;p&gt;You've got an AGENTS.md — maybe you &lt;a href="https://dev.to/wolfejam/agentsmd-hands-on-build-one-step-by-step-and-watch-an-agent-use-it-3g27"&gt;built one step by step&lt;/a&gt;. The problem now isn't &lt;em&gt;writing&lt;/em&gt; it. It's that, &lt;a href="https://dev.to/wolfejam/your-agentsmd-is-already-stale-and-your-agent-trusts-it-completely-2nfh"&gt;as we covered, it goes stale the day you write it&lt;/a&gt; — it starts lying the moment your code moves, and your agent believes it anyway. So let's harden it. Open a repo and follow along.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — Start from the truth, not a template
&lt;/h2&gt;

&lt;p&gt;Before you write a line, list what's actually real:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;package.json | jq .scripts     &lt;span class="c"&gt;# your real commands&lt;/span&gt;
&lt;span class="nb"&gt;ls &lt;/span&gt;src/                            &lt;span class="c"&gt;# your real entry points&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; .eslintrc&lt;span class="k"&gt;*&lt;/span&gt; tsconfig.json 2&amp;gt;/dev/null   &lt;span class="c"&gt;# your real conventions (the configs)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rule for the whole file: &lt;strong&gt;if it isn't in the repo, it doesn't go in the AGENTS.md.&lt;/strong&gt; No aspirations, no "we should probably." Facts only.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Write the commands copy-pasteable, then RUN them
&lt;/h2&gt;

&lt;p&gt;Don't type &lt;code&gt;npm build&lt;/code&gt; from memory. Run it, and paste what actually works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Put the &lt;em&gt;exact&lt;/em&gt; strings in the file. If the command in your AGENTS.md doesn't run, your agent's first move fails — and it won't know why. Tests go &lt;em&gt;above&lt;/em&gt; build: they're the agent's only way to check its work against reality.&lt;/p&gt;

&lt;p&gt;And wrap every tool and command name in backticks — &lt;code&gt;mypy&lt;/code&gt;, &lt;code&gt;ruff&lt;/code&gt;, &lt;code&gt;npm test&lt;/code&gt;. A formatted token reads as a &lt;em&gt;literal to run&lt;/em&gt;, not a vague suggestion the model can paraphrase away when the context is saturated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — Point at the config; don't restate it
&lt;/h2&gt;

&lt;p&gt;You already have a linter and a formatter. So &lt;strong&gt;don't&lt;/strong&gt; write "use 2 spaces, prefer const, strict types." Write:&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="gu"&gt;## Conventions&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Style is enforced — run &lt;span class="sb"&gt;`npm run lint`&lt;/span&gt;. Obey the config, don't hand-format.
&lt;span class="p"&gt;-&lt;/span&gt; TypeScript strict mode (tsconfig.json).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restating a rule your linter owns is how the file goes stale (you change the config, the prose lies). Point at the source of truth instead. One line, can't rot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 — Guardrails in three tiers + a Definition of Done
&lt;/h2&gt;

&lt;p&gt;Give the agent a safety map and a finish line:&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="gu"&gt;## Guardrails&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Always:**&lt;/span&gt; read files, run the tests, build.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Ask first:**&lt;/span&gt; dependency installs, deletions, migrations.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Never:**&lt;/span&gt; force-push, push to &lt;span class="sb"&gt;`main`&lt;/span&gt;, commit secrets.

&lt;span class="gu"&gt;## Definition of Done&lt;/span&gt;
Done when: &lt;span class="sb"&gt;`npm run lint`&lt;/span&gt; exits 0 · &lt;span class="sb"&gt;`npm test`&lt;/span&gt; passes · committed with a conventional message.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the agent can &lt;em&gt;know&lt;/em&gt; it's finished, not guess — and it knows which moves need a human.&lt;/p&gt;

&lt;p&gt;One phrasing note that punches above its weight: &lt;strong&gt;lead with the enable, not the prohibition.&lt;/strong&gt; "Branch and open a PR" hands the agent the safe path; a bare "don't push to &lt;code&gt;main&lt;/code&gt;" hands it only the landmine — and a naked negative can prime the very move it's warning against. Say what &lt;em&gt;to&lt;/em&gt; do, then the boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 — The anti-rot rule: it changes in the same PR as the code
&lt;/h2&gt;

&lt;p&gt;This is the step everyone skips, and it's the whole game. Make drift a &lt;em&gt;bug&lt;/em&gt;, not a someday:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add it to your PR template: &lt;em&gt;"Touched build/test scripts? Update AGENTS.md."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Or a CI check that fails if &lt;code&gt;package.json&lt;/code&gt; scripts changed but &lt;code&gt;AGENTS.md&lt;/code&gt; didn't.&lt;/li&gt;
&lt;li&gt;Or — the honest endgame — regenerate the file from the repo so it &lt;em&gt;can't&lt;/em&gt; drift (more on that below).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Treat AGENTS.md like code, because your agent already does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6 — Verify: point your agent at it and watch
&lt;/h2&gt;

&lt;p&gt;Give the agent a small, real task ("add a tested &lt;code&gt;/health&lt;/code&gt; endpoint"). Watch it: read the file, run the tests you listed, respect the guardrails, hit your Definition of Done. If it does the wrong thing — wrong command, ignored convention — &lt;strong&gt;your file lied.&lt;/strong&gt; Fix the line, not the agent.&lt;/p&gt;

&lt;p&gt;That loop — task, watch, fix the line — is how an AGENTS.md earns trust. A true one becomes the most reliable thing in your repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to go deeper
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The series:&lt;/strong&gt; &lt;a href="https://dev.to/wolfejam/agentsmd-the-one-file-that-makes-ai-coding-agents-actually-useful-ckj"&gt;1 · the field guide&lt;/a&gt; → &lt;a href="https://dev.to/wolfejam/agentsmd-hands-on-build-one-step-by-step-and-watch-an-agent-use-it-3g27"&gt;2 · build one, hands-on&lt;/a&gt; → &lt;a href="https://dev.to/wolfejam/your-agentsmd-is-already-stale-and-your-agent-trusts-it-completely-2nfh"&gt;3 · why it goes stale&lt;/a&gt; → you're on &lt;strong&gt;4&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The AGENTS.md standard&lt;/strong&gt; — &lt;a href="https://agents.md" rel="noopener noreferrer"&gt;agents.md&lt;/a&gt; (the spec itself).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Section-by-section definitions + a field guide&lt;/strong&gt; — &lt;a href="https://faf.one/agents" rel="noopener noreferrer"&gt;faf.one/agents&lt;/a&gt; (what earns a line, ordering, length, and the anti-patterns).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why "generated from truth" beats hand-writing &lt;em&gt;and&lt;/em&gt; AI-slop&lt;/strong&gt; — the research and the deeper dive, same place.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep it honest, and your agent stops guessing. That's the whole point of the file.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>devtools</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Your AGENTS.md Is Already Stale — And Your Agent Trusts It Completely</title>
      <dc:creator>wolfejam.dev</dc:creator>
      <pubDate>Fri, 10 Jul 2026 17:43:45 +0000</pubDate>
      <link>https://dev.to/wolfejam/your-agentsmd-is-already-stale-and-your-agent-trusts-it-completely-2nfh</link>
      <guid>https://dev.to/wolfejam/your-agentsmd-is-already-stale-and-your-agent-trusts-it-completely-2nfh</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR — A hand-written AGENTS.md rots the day you write it. And unlike a stale README (which a human skims skeptically), a stale AGENTS.md is &lt;em&gt;obeyed&lt;/em&gt; by your coding agent with full confidence: wrong build command, missing guardrail, dead convention — followed to the letter. The fix isn't more discipline. It's writing the file so every line traces to a fact you can verify, and refusing to let it drift.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the field guide, I covered &lt;em&gt;what&lt;/em&gt; an AGENTS.md is and what belongs in it. In the build-along, we wrote a complete one against a real repo and watched an agent use it. This is the part nobody warns you about — the part that decides whether all of that pays off: &lt;strong&gt;keeping it true.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why you need to know this
&lt;/h2&gt;

&lt;p&gt;You probably already have an AGENTS.md — or you're about to. Here's the part nobody says out loud: it's a cache, and &lt;strong&gt;a stale cache is worse than an empty one.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A human reads a stale README and thinks "hmm, that looks old." An agent reads your AGENTS.md and &lt;em&gt;acts on it&lt;/em&gt; — it runs the build command you renamed three sprints ago, skips the guardrail you added last week, follows the convention you already abandoned. Confidently. No second-guessing.&lt;/p&gt;

&lt;p&gt;So the real question isn't "should I have an AGENTS.md." (Yes.) It's &lt;strong&gt;"how do I keep it true?"&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to do it
&lt;/h2&gt;

&lt;p&gt;A good AGENTS.md answers exactly one question: &lt;em&gt;how do I work in this repo?&lt;/em&gt; Not what the project is — that's the README. How to actually operate here. Six moves:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Orientation — one line.&lt;/strong&gt; What it is, the language, the stack. Then stop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The real commands.&lt;/strong&gt; Setup, build, and — most important — how to run the tests. Copy-pasteable. Tests rank &lt;em&gt;above&lt;/em&gt; build, because tests are the agent's only way to check its work against reality instead of trusting a stale belief.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Where things live.&lt;/strong&gt; Entry points and key directories — &lt;em&gt;not&lt;/em&gt; a file dump (the agent can run &lt;code&gt;ls&lt;/code&gt;). Just the non-obvious "start here."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Conventions — only what resolves ambiguity.&lt;/strong&gt; "Validate input at the route boundary with Zod" is useful. "Write clean code" is ignored. And if a rule is already enforced by your linter or formatter, &lt;strong&gt;point at the config — don't restate it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Guardrails — what NOT to do.&lt;/strong&gt; The landmines. Three tiers is the sweet spot: what's &lt;em&gt;always&lt;/em&gt; safe, what to &lt;em&gt;ask first&lt;/em&gt;, what to &lt;em&gt;never&lt;/em&gt; do (force-push, touch prod, commit secrets).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. A definition of done.&lt;/strong&gt; "Done when lint passes, tests pass, changes committed." Make it mechanically checkable — the agent should be able to &lt;em&gt;know&lt;/em&gt; it's finished, not guess.&lt;/p&gt;

&lt;p&gt;Keep it short — 20–50 lines. Every line earns its place by one test: &lt;strong&gt;does it resolve a real ambiguity, or save the agent an expensive hunt?&lt;/strong&gt; If it does neither, cut it. (Past ~150 lines you're adding cost with no gain.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The part almost everyone gets wrong
&lt;/h2&gt;

&lt;p&gt;There are two failure modes, and they're opposite:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hand-written and left alone → it rots.&lt;/strong&gt; You change the code; the file quietly lies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Let an AI write it for me" → it bloats.&lt;/strong&gt; The research here is blunt: auto-generated instruction files measurably &lt;em&gt;reduce&lt;/em&gt; task success and add cost, because the model pads them with plausible-sounding requirements the agent then over-obeys.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fix is the same for both: &lt;strong&gt;every line should trace to a fact.&lt;/strong&gt; A real command. A real file. A real constraint. Not prose, not padding, not guesses. And when the code changes, the file changes &lt;em&gt;in the same PR&lt;/em&gt; — treat it like code, because your agent already does.&lt;/p&gt;

&lt;p&gt;Get those two things right — facts only, kept current — and your AGENTS.md stops being a liability and starts being the most reliable thing in the repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we care about this
&lt;/h2&gt;

&lt;p&gt;We maintain a lot of repos, and we kept paying the same tax: the AGENTS.md goes stale, everywhere, every week. Getting the file &lt;em&gt;right&lt;/em&gt; is an afternoon. Keeping it &lt;em&gt;true&lt;/em&gt; is the harder half — and it's the half that actually decides whether your agent helps you or confidently ships you the wrong thing.&lt;/p&gt;

&lt;p&gt;Write one well. Keep it honest. Your agent is only as good as the last true thing you told it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Further reading:&lt;/strong&gt; the standard itself at &lt;a href="https://agents.md" rel="noopener noreferrer"&gt;agents.md&lt;/a&gt;, and a section-by-section field guide — what earns a line, ordering, length, the anti-patterns — at &lt;a href="https://faf.one/agents" rel="noopener noreferrer"&gt;faf.one/agents&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Coming next:&lt;/strong&gt; the exact discipline to &lt;em&gt;keep&lt;/em&gt; it true — step by step, stopping the rot, with an agent you can watch trust it.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>AGENTS.md, Hands-On: Build One Step by Step (and Watch an Agent Use It)</title>
      <dc:creator>wolfejam.dev</dc:creator>
      <pubDate>Sat, 04 Jul 2026 03:31:12 +0000</pubDate>
      <link>https://dev.to/wolfejam/agentsmd-hands-on-build-one-step-by-step-and-watch-an-agent-use-it-3g27</link>
      <guid>https://dev.to/wolfejam/agentsmd-hands-on-build-one-step-by-step-and-watch-an-agent-use-it-3g27</guid>
      <description>&lt;p&gt;In &lt;a href="https://dev.to/wolfejam/agentsmd-the-one-file-that-makes-ai-coding-agents-actually-useful-ckj"&gt;the field guide&lt;/a&gt; I covered what an AGENTS.md is and what belongs in it. This is the hands-on follow-up: we'll build a complete AGENTS.md for a real project, one section at a time, then point an AI coding agent at it and watch the difference it makes. By the end you'll have a working file — and you'll have seen it pay off.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;New to AGENTS.md? It's a single Markdown file at the root of your repo that tells AI coding agents how to work in it — build steps, tests, conventions, guardrails. The "why" behind each section is in &lt;a href="https://dev.to/wolfejam/agentsmd-the-one-file-that-makes-ai-coding-agents-actually-useful-ckj"&gt;the field guide&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The project we'll use
&lt;/h2&gt;

&lt;p&gt;We'll write the AGENTS.md for a small but real service: a &lt;strong&gt;URL shortener API in Python&lt;/strong&gt; — FastAPI, SQLite, pytest. A couple of endpoints, a thin data layer, a test suite. Follow along with this, or swap in your own repo — the steps are identical.&lt;/p&gt;

&lt;p&gt;Its shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;linkshort/
  app/
    main.py        # FastAPI routes
    db.py          # SQLite access
    models.py      # Pydantic models
  migrations/      # generated SQL — not hand-edited
  tests/
  requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 0 — Start with an empty file
&lt;/h2&gt;

&lt;p&gt;At the repo root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;touch &lt;/span&gt;AGENTS.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole step. We'll fill it in one section at a time, building toward a file an agent can read in thirty seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — Orientation: one line
&lt;/h2&gt;

&lt;p&gt;Tell the agent what it's looking at. Add:&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;# AGENTS.md&lt;/span&gt;

A URL shortener API in Python — FastAPI, SQLite, pytest.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One sentence sets the agent's priors: it knows the language, framework, and storage before it reads a single line of code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Setup and run
&lt;/h2&gt;

&lt;p&gt;The agent can't help if it can't start the project. Add the real, copy-pasteable commands:&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="gu"&gt;## Setup&lt;/span&gt;
python -m venv .venv &amp;amp;&amp;amp; source .venv/bin/activate
pip install -r requirements.txt

&lt;span class="gu"&gt;## Run&lt;/span&gt;
uvicorn app.main:app --reload   # http://localhost:8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the commands that actually work in your repo — no placeholders.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — Tests: the agent's feedback loop
&lt;/h2&gt;

&lt;p&gt;This is the most important section, because tests are how the agent checks its own work. Add:&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="gu"&gt;## Test — all must pass before a change is done&lt;/span&gt;
pytest
ruff check .
mypy app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the agent knows how to verify a change &lt;em&gt;and&lt;/em&gt; the bar it has to clear. An agent that knows &lt;code&gt;pytest&lt;/code&gt; will run it; one that doesn't hands you a broken branch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 — The map: where things live
&lt;/h2&gt;

&lt;p&gt;A short map so the agent finds its way without spelunking the whole tree:&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="gu"&gt;## Structure&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; app/main.py    route handlers
&lt;span class="p"&gt;-&lt;/span&gt; app/db.py      SQLite access (parameterized queries only, never string-built SQL)
&lt;span class="p"&gt;-&lt;/span&gt; app/models.py  Pydantic request/response models
&lt;span class="p"&gt;-&lt;/span&gt; migrations/    generated SQL — do not hand-edit
&lt;span class="p"&gt;-&lt;/span&gt; tests/         pytest, mirroring app/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice we're already slipping a convention ("parameterized queries only") and a guardrail ("do not hand-edit") in right where they're relevant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 — Conventions: the house style
&lt;/h2&gt;

&lt;p&gt;The patterns you want followed. Be specific — vague rules are noise:&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="gu"&gt;## Conventions&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Validate all input with Pydantic models at the route boundary.
&lt;span class="p"&gt;-&lt;/span&gt; Raise HTTPException for client errors; never return raw dicts on failure.
&lt;span class="p"&gt;-&lt;/span&gt; Type everything; mypy must stay clean.
&lt;span class="p"&gt;-&lt;/span&gt; Match the style of the surrounding file.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"Type everything; mypy must stay clean" tells the agent exactly what to do. "Write good code" wouldn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6 — Commits and PRs
&lt;/h2&gt;

&lt;p&gt;If your agent opens PRs, give it the house rules:&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="gu"&gt;## Commits &amp;amp; PRs&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Conventional Commits (feat:, fix:, chore:).
&lt;span class="p"&gt;-&lt;/span&gt; One logical change per PR; update CHANGELOG.md.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 7 — Guardrails: the landmines
&lt;/h2&gt;

&lt;p&gt;The "don'ts" that prevent expensive mistakes:&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="gu"&gt;## Don't&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Don't hand-edit migrations/ — they're generated.
&lt;span class="p"&gt;-&lt;/span&gt; Don't commit directly to main — branch and open a PR.
&lt;span class="p"&gt;-&lt;/span&gt; Never run the seed script against a non-local database.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Your finished AGENTS.md
&lt;/h2&gt;

&lt;p&gt;Put it together and you have a complete, copy-pasteable file:&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;# AGENTS.md&lt;/span&gt;

A URL shortener API in Python — FastAPI, SQLite, pytest.

&lt;span class="gu"&gt;## Setup&lt;/span&gt;
python -m venv .venv &amp;amp;&amp;amp; source .venv/bin/activate
pip install -r requirements.txt

&lt;span class="gu"&gt;## Run&lt;/span&gt;
uvicorn app.main:app --reload   # http://localhost:8000

&lt;span class="gu"&gt;## Test — all must pass before a change is done&lt;/span&gt;
pytest
ruff check .
mypy app

&lt;span class="gu"&gt;## Structure&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; app/main.py    route handlers
&lt;span class="p"&gt;-&lt;/span&gt; app/db.py      SQLite access (parameterized queries only, never string-built SQL)
&lt;span class="p"&gt;-&lt;/span&gt; app/models.py  Pydantic request/response models
&lt;span class="p"&gt;-&lt;/span&gt; migrations/    generated SQL — do not hand-edit
&lt;span class="p"&gt;-&lt;/span&gt; tests/         pytest, mirroring app/

&lt;span class="gu"&gt;## Conventions&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Validate all input with Pydantic models at the route boundary.
&lt;span class="p"&gt;-&lt;/span&gt; Raise HTTPException for client errors; never return raw dicts on failure.
&lt;span class="p"&gt;-&lt;/span&gt; Type everything; mypy must stay clean.
&lt;span class="p"&gt;-&lt;/span&gt; Match the style of the surrounding file.

&lt;span class="gu"&gt;## Commits &amp;amp; PRs&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Conventional Commits (feat:, fix:, chore:).
&lt;span class="p"&gt;-&lt;/span&gt; One logical change per PR; update CHANGELOG.md.

&lt;span class="gu"&gt;## Don't&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Don't hand-edit migrations/ — they're generated.
&lt;span class="p"&gt;-&lt;/span&gt; Don't commit directly to main — branch and open a PR.
&lt;span class="p"&gt;-&lt;/span&gt; Never run the seed script against a non-local database.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thirty seconds to read. Now let's see if it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 8 — Prove it: point an agent at it
&lt;/h2&gt;

&lt;p&gt;This is the part that matters. Open your repo in an AI coding agent — Claude Code, Cursor, Codex, whatever you use — and give it a real task:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Add a &lt;code&gt;DELETE /links/{code}&lt;/code&gt; endpoint that removes a link, with a test."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Watch what it does &lt;strong&gt;with the AGENTS.md in place:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It reads the file first — it knows the stack and where routes live.&lt;/li&gt;
&lt;li&gt;It adds the handler in &lt;code&gt;app/main.py&lt;/code&gt;, validating input the way your conventions require.&lt;/li&gt;
&lt;li&gt;It writes a &lt;code&gt;pytest&lt;/code&gt; test in &lt;code&gt;tests/&lt;/code&gt;, mirroring the structure.&lt;/li&gt;
&lt;li&gt;It runs &lt;code&gt;pytest&lt;/code&gt;, &lt;code&gt;ruff&lt;/code&gt;, and &lt;code&gt;mypy&lt;/code&gt; — because you told it that's the bar — and fixes what fails.&lt;/li&gt;
&lt;li&gt;It &lt;strong&gt;doesn't&lt;/strong&gt; touch &lt;code&gt;migrations/&lt;/code&gt;, and it &lt;strong&gt;doesn't&lt;/strong&gt; commit to main — it opens a branch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now picture the same task &lt;strong&gt;without&lt;/strong&gt; the file. The agent has to guess: Which test runner? Where do routes go? Is there a lint step? So it asks you, or it guesses wrong, or it edits a generated file you'll have to revert. The AGENTS.md is the difference between an agent that interrupts you and one that just ships.&lt;/p&gt;

&lt;p&gt;That's the whole payoff — and you can watch it happen in real time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep it alive
&lt;/h2&gt;

&lt;p&gt;One habit before you go: treat the file like code. When the test command changes, or you add a directory, or you catch yourself telling the agent the same thing twice — update AGENTS.md in the same breath. A stale file is worse than none, because the agent trusts it.&lt;/p&gt;

&lt;h2&gt;
  
  
  That's the loop
&lt;/h2&gt;

&lt;p&gt;You started with an empty file, added eight short sections, and watched an agent use every one of them to land a correct, tested change without hand-holding. Write it once, and every agent that walks into your repo gets the same briefing.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This was the hands-on build. For the principles behind each section — what belongs, the anti-patterns, why short beats complete — see &lt;a href="https://dev.to/wolfejam/agentsmd-the-one-file-that-makes-ai-coding-agents-actually-useful-ckj"&gt;the field guide&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>AGENTS.md: The One File That Makes AI Coding Agents Actually Useful</title>
      <dc:creator>wolfejam.dev</dc:creator>
      <pubDate>Tue, 30 Jun 2026 01:51:07 +0000</pubDate>
      <link>https://dev.to/wolfejam/agentsmd-the-one-file-that-makes-ai-coding-agents-actually-useful-ckj</link>
      <guid>https://dev.to/wolfejam/agentsmd-the-one-file-that-makes-ai-coding-agents-actually-useful-ckj</guid>
      <description>&lt;p&gt;If you’ve used Claude Code, Cursor, Codex, Aider, Gemini CLI, GitHub Copilot, Grok, goose, or similar tools, you’ve seen the same pattern: the agent’s first priority is context. What is this project? How do I build it? How do I run the tests? What conventions should I follow? What must I not break?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AGENTS.md&lt;/strong&gt; is the open answer. It’s a single Markdown file placed at the root of your repository that serves as a dedicated, predictable briefing for AI coding agents. Think of it as a README written specifically for the agent instead of a human. The format is deliberately simple and tool-agnostic — one file that works across many agents.&lt;/p&gt;

&lt;p&gt;AGENTS.md was pioneered by OpenAI's Codex and shaped alongside tools like Cursor, Amp, Factory, and Google's Jules. In December 2025 it was donated to the Agentic AI Foundation under the Linux Foundation, where it's now stewarded as an open standard.&lt;/p&gt;

&lt;p&gt;Most AGENTS.md files fail in one of two ways: they’re either too thin to be useful or they’re long, rambling documents the agent skims and mostly ignores. A good AGENTS.md sits in the middle — short, current, specific, and actionable.&lt;/p&gt;

&lt;h2&gt;
  
  
  README is for humans. AGENTS.md is for the agent.
&lt;/h2&gt;

&lt;p&gt;This distinction determines everything that belongs in the file.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;README.md&lt;/strong&gt; answers &lt;em&gt;what&lt;/em&gt; and &lt;em&gt;why&lt;/em&gt;: what the project is, why it exists, and how a human gets started.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AGENTS.md&lt;/strong&gt; answers &lt;em&gt;how to work here&lt;/em&gt;: the exact commands, conventions, and guardrails an agent needs to make changes and verify them without asking questions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not duplicate content from the README. If the agent doesn’t need it to act effectively, leave it out. Every non-essential line dilutes the signal.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually belongs in AGENTS.md
&lt;/h2&gt;

&lt;p&gt;The highest-value sections, in rough priority order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One-line orientation.&lt;/strong&gt; A single sentence telling the agent what the project is and what stack it uses — for example, “A TypeScript REST API on Node 20 with Postgres, deployed to Fly.io.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup and build commands.&lt;/strong&gt; The single most valuable section. Give the real, copy-pasteable commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run build
npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;How to run tests.&lt;/strong&gt; Even more important than build commands — tests are how the agent verifies its own work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;test&lt;/span&gt;          &lt;span class="c"&gt;# all tests must pass before considering a change complete&lt;/span&gt;
npm run lint
npm run typecheck
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Where things live.&lt;/strong&gt; A short, focused map of key directories and entry points — not a full tree dump.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conventions.&lt;/strong&gt; Specific patterns you want followed: validation approach, error handling, naming, preferred libraries, architectural decisions. Vague statements like “write clean code” are useless; specific rules like “Validate all input with Zod at the route boundary” are useful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Commit and PR rules.&lt;/strong&gt; Message format, branch naming, changelog updates, and any other process requirements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Guardrails — what NOT to do.&lt;/strong&gt; The landmines that prevent expensive mistakes: don’t edit files in &lt;code&gt;/generated&lt;/code&gt;, never run migrations against production config, and don’t commit directly to main.&lt;/p&gt;

&lt;h2&gt;
  
  
  The anti-patterns that make it worse than nothing
&lt;/h2&gt;

&lt;p&gt;A bad AGENTS.md doesn’t just fail to help — it actively misleads the agent.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The novel&lt;/strong&gt;: Too long. The agent skims and important instructions get lost. Cut anything that isn’t load-bearing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The stale file&lt;/strong&gt;: An outdated command or path is worse than no file at all. The agent will confidently do the wrong thing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vagueness&lt;/strong&gt;: “Follow best practices” gives the agent nothing actionable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secrets&lt;/strong&gt;: Never put credentials, keys, or tokens in AGENTS.md. Point to where they come from (&lt;code&gt;.env&lt;/code&gt;, secrets manager) instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;README duplication&lt;/strong&gt;: Wasted tokens and maintenance burden.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Keep it alive — treat AGENTS.md like code
&lt;/h2&gt;

&lt;p&gt;AGENTS.md files rot when no one owns them. Treat the file the same way you treat production code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Review changes to it in pull requests.&lt;/li&gt;
&lt;li&gt;Update it the moment reality changes (new test command, new directory structure, new convention).&lt;/li&gt;
&lt;li&gt;Watch what the agent actually does. When it guesses, asks unnecessary questions, or touches something it shouldn’t, that’s usually a missing or stale instruction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A useful habit: every time you find yourself giving the same instruction to an agent twice in chat, move that instruction into AGENTS.md.&lt;/p&gt;

&lt;h2&gt;
  
  
  One file, many agents
&lt;/h2&gt;

&lt;p&gt;The strength of AGENTS.md is that it isn’t tied to any single tool. Most major coding agents either read it directly or converge on the same root-level instruction pattern. Write plain Markdown with tool-agnostic instructions. Avoid “if you are using X tool” branching.&lt;/p&gt;

&lt;p&gt;For monorepos, you can place additional &lt;code&gt;AGENTS.md&lt;/code&gt; files in subdirectories. Most agents use the nearest file to the code they’re working on.&lt;/p&gt;

&lt;h2&gt;
  
  
  A minimal, complete example
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# AGENTS.md&lt;/span&gt;

A TypeScript REST API on Node 20, Postgres, deployed to Fly.io.

&lt;span class="gu"&gt;## Setup&lt;/span&gt;
npm install
cp .env.example .env   # fill in DATABASE_URL

&lt;span class="gu"&gt;## Build &amp;amp; run&lt;/span&gt;
npm run build
npm run dev            # http://localhost:3000

&lt;span class="gu"&gt;## Test — all must pass before a change is considered done&lt;/span&gt;
npm test
npm run lint
npm run typecheck

&lt;span class="gu"&gt;## Structure&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; src/routes/     HTTP handlers
&lt;span class="p"&gt;-&lt;/span&gt; src/db/         Postgres queries (use the query builder, no raw SQL)
&lt;span class="p"&gt;-&lt;/span&gt; src/lib/        shared utilities
&lt;span class="p"&gt;-&lt;/span&gt; test/           Vitest tests, mirroring src/ structure

&lt;span class="gu"&gt;## Conventions&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Validate all input with Zod at the route boundary
&lt;span class="p"&gt;-&lt;/span&gt; Throw AppError (from src/lib/errors.ts), never a bare Error
&lt;span class="p"&gt;-&lt;/span&gt; Match the style and patterns of the surrounding file

&lt;span class="gu"&gt;## Commits &amp;amp; PRs&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Use Conventional Commits (feat:, fix:, chore:)
&lt;span class="p"&gt;-&lt;/span&gt; One logical change per PR
&lt;span class="p"&gt;-&lt;/span&gt; Update CHANGELOG.md when relevant

&lt;span class="gu"&gt;## Don't&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Don't edit anything in src/generated/ (regenerated from schema)
&lt;span class="p"&gt;-&lt;/span&gt; Don't commit directly to main — always branch and open a PR
&lt;span class="p"&gt;-&lt;/span&gt; Never run db:reset against a non-local DATABASE_URL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An agent (or a new human teammate) can read this in under 30 seconds and start being productive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real test of a good AGENTS.md
&lt;/h2&gt;

&lt;p&gt;You’ll know it’s working when a fresh agent can land a correct, tested change in your repository without asking how to build, how to test, or which conventions to follow — and without touching the one thing you explicitly told it not to touch.&lt;/p&gt;

&lt;p&gt;Short. Current. Specific. Actionable. That’s the entire job.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This was the field guide — the why and the what.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Coming next in the series:&lt;/strong&gt; a step-by-step build-along, where we’ll write a complete AGENTS.md against a real repo, then point an agent at it and watch it build, test, and respect the guardrails without being asked. (A day or two out.)&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>opensource</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
