<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="rss.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Daniel Ostrovsky — Blog</title>
        <link>https://danduh.me/blog</link>
        <description>Essays on AI architecture, agents, and production AI by Daniel Ostrovsky.</description>
        <lastBuildDate>Tue, 28 Jul 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <copyright>Copyright © 2026 Daniel Ostrovsky.</copyright>
        <item>
            <title><![CDATA[MCP 2.0 Is Mostly Deletion. That's The Good Part.]]></title>
            <link>https://danduh.me/blog/mcp-goes-stateless</link>
            <guid>https://danduh.me/blog/mcp-goes-stateless</guid>
            <pubDate>Tue, 28 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[MCP's biggest release since launch is mostly a subtraction — the session is gone and the protocol is stateless. What actually breaks, who has real migration work, and the one thing they quietly left unfixed.]]></description>
            <content:encoded><![CDATA[<div style="margin-bottom:1.25rem"><img src="https://danduh.me/img/mcp-goes-stateless/hero-1200.webp" srcset="https://danduh.me/img/mcp-goes-stateless/hero-800.webp 800w, https://danduh.me/img/mcp-goes-stateless/hero-1200.webp 1200w, https://danduh.me/img/mcp-goes-stateless/hero.webp 1600w" sizes="(max-width: 996px) 100vw, 768px" width="1600" height="893" alt="MCP 2.0 — the stateless evolution: an infographic overview of the release, from sessions removed and per-request metadata to extensions, tasks, MCP Apps, auth hardening, and the deprecation roadmap." loading="eager" fetchpriority="high" style="display:block;width:100%;height:auto"></div>
<p>Ok. MCP just shipped the biggest change since the day it launched, and the headline isn't a feature you get to switch on. It's the delete key.</p>
<p>The <a href="https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/" target="_blank" rel="noopener noreferrer"><code>2026-07-28</code> revision</a> — people are calling it MCP 2.0, and it's the first release that breaks the base protocol on purpose — rips the session out. No more <code>initialize</code> handshake. No more <code>notifications/initialized</code>. No <code>Mcp-Session-Id</code>. No GET SSE endpoint, no resumable streams, no <code>resources/subscribe</code>, no <code>roots/list</code> at the top level, no <code>ping</code>, no <code>logging/setLevel</code>. Gone.</p>
<p>And here's the thing — that's the good news.</p>
<!-- -->
<p>I've shipped servers behind three different gateways, and every one of them cracked open the JSON-RPC body to route a request. That's the tax the session quietly charged. On every call. This revision stops charging it.</p>
<p>Let's get the map down before we walk the wreckage. Six threads landed in one revision — a stateless core, an extensions framework, a rebuilt Tasks story, server-sent UI, an OAuth hardening pass, and a written deprecation policy — and every one of them hangs off the first. Delete the session and the other five stop being optional.</p>
<p><img decoding="async" loading="lazy" alt="MCP 2.0 overview: the release-date node branching to six threads — stateless core, extensions, tasks, MCP Apps, auth hardening, and the deprecation policy." src="https://danduh.me/assets/images/overview-85b1d158c08dd8025fa6b444d4e3e1e6.webp" width="1400" height="186" class="img_ev3q"></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="what-they-deleted-and-why-nobodys-crying">What they deleted (and why nobody's crying)<a href="https://danduh.me/blog/mcp-goes-stateless#what-they-deleted-and-why-nobodys-crying" class="hash-link" aria-label="Direct link to What they deleted (and why nobody's crying)" title="Direct link to What they deleted (and why nobody's crying)">​</a></h2>
<p>The <code>initialize</code> handshake did three jobs at once, per connection: negotiate the protocol version, hand you the server's capabilities, hand the server yours. Three payloads welded together. That weld is gone, and the jobs got unbundled.</p>
<p>Protocol version, client info, client capabilities now ride in <code>_meta</code> on every request, next to a mandatory <code>MCP-Protocol-Version</code> header that has to match or the server answers <code>400</code>. Server capabilities move to a new <code>server/discover</code> call you make only if you want them; servers must implement it, clients may never call it. Log level becomes a per-request field. Version negotiation happens inline — ask for a version the server doesn't speak and it hands back the list of ones it does.</p>
<p>So every request now carries who you are and what you speak. Self-contained. Which means it can land anywhere.</p>
<p>That last part is the whole point.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="sessions-were-never-state-they-were-a-bill">Sessions were never state. They were a bill.<a href="https://danduh.me/blog/mcp-goes-stateless#sessions-were-never-state-they-were-a-bill" class="hash-link" aria-label="Direct link to Sessions were never state. They were a bill." title="Direct link to Sessions were never state. They were a bill.">​</a></h2>
<p>The part nobody printed on the box when MCP launched: the session wasn't a feature you used, it was a tax you paid down at the infrastructure layer. If you server held per-connection state, you had exactly three ways to run more than one box, and every one of them cost you money.</p>
<p>You pinned each client to the instance holding its state — sticky sessions. Or you stood up Redis or Postgres so any box could reload it — a shared store you now operate, monitor, and get paged about at 3am.. Or you put a gateway in front that cracked open the JSON-RPC body on every single request just to work out where to send it. Deep packet inspection. To route a shopping basket.</p>
<p><img decoding="async" loading="lazy" alt="Stateful topology: a client pinned by a sticky load balancer to one server, with all three servers reading and writing a shared Redis or Postgres session store." src="https://danduh.me/assets/images/topology-before-3c050f80821c8dc55e983dbfe4bd1a2c.webp" width="1400" height="493" class="img_ev3q"></p>
<p>The <a href="https://modelcontextprotocol.io/seps/2567-sessionless-mcp" target="_blank" rel="noopener noreferrer">sessionless design</a> deletes all three at once.</p>
<p><img decoding="async" loading="lazy" alt="Stateless topology: a client behind a plain round-robin load balancer that routes on the Mcp-Method header to any of three interchangeable servers, with no shared store." src="https://danduh.me/assets/images/topology-after-71387f2a41d92127fb8d727ab5fa95f1.webp" width="1400" height="651" class="img_ev3q"></p>
<p>Any request lands on any box. No shared store, no pinning, no gateway reading your mail. Round-robin and go home.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="where-did-the-state-go-into-the-arguments">Where did the state go? Into the arguments.<a href="https://danduh.me/blog/mcp-goes-stateless#where-did-the-state-go-into-the-arguments" class="hash-link" aria-label="Direct link to Where did the state go? Into the arguments." title="Direct link to Where did the state go? Into the arguments.">​</a></h2>
<p>It didn't vanish. It became a string.</p>
<p>Instead of a basket the server secretly holds against your connection, the server exposes a <code>create_basket()</code> tool that hands back a <code>basket_id</code> — call it BBB — and the model threads BBB straight into <code>add_item(BBB, …)</code> and then <code>checkout(BBB)</code>. No <code>handles/*</code> method, no handle type in the schema — a handle is a string in a result and a string in the next argument. Just a string.</p>
<p>And this is already how every server you actually use behaves. Linear's <code>create_issue</code> returns an issue id. GitHub's <code>create_pull_request</code> returns a PR number. Stripe's <code>create_customer</code> returns a customer id. Nobody was keeping your Stripe customer alive in a session — they handed you an id and told you to bring it back. The difference now is the model can see the id, instead of it hiding in a transport session behind the server.</p>
<p>How much of the ecosystem does this actually break? The maintainers ran the numbers on a sample of <a href="https://modelcontextprotocol.io/seps/2575-stateless-mcp" target="_blank" rel="noopener noreferrer">1,000 open-source MCP servers</a>. 90% never reference the session id at all. Another 3.5% only touch it as SDK routing boilerplate a sessionless transport deletes for them, and 2.8% touch it only to wire up the transport. Basically it's a two-line change for almost everyone, and a real rewrite for two small groups — servers that keyed application state on the session (2.5%), and gateways doing sticky routing (0.7%).</p>
<p>One catch, a security one: the <code>requestState</code> blob comes from the client on the next call, so it's untrusted input the second it leaves your process. Integrity-protect it, bind it to the authenticated principal and the original arguments, give it an expiry.</p>
<p>Signed <code>requestState</code> = untrusted input. Encrypted <code>requestState</code> = still untrusted, just unreadable. Signing is not a trust upgrade, so don't stuff anything secret in there and call it safe.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="but-the-server-still-needs-to-ask-you-things">But the server still needs to ask you things<a href="https://danduh.me/blog/mcp-goes-stateless#but-the-server-still-needs-to-ask-you-things" class="hash-link" aria-label="Direct link to But the server still needs to ask you things" title="Direct link to But the server still needs to ask you things">​</a></h2>
<p>A stateless server still has to stop mid-call and ask for something — confirm you really want to delete basket BBB, sample the model, read a root. The old way was a held-open SSE stream you side-channelled the question down. No stream anymore. So how?</p>
<p>The answer is a mechanism called <a href="https://modelcontextprotocol.io/specification/draft/basic/patterns/mrtr" target="_blank" rel="noopener noreferrer">Multi Round-Trip Requests</a>, the cleanest idea in the release. Instead of streaming a question back, the server returns a result that says <code>input_required</code>, carrying the questions plus that opaque <code>requestState</code> blob. You gather the answers and re-issue the exact same call — original arguments, the new <code>inputResponses</code>, the same blob echoed back, a fresh request id. All the correlation lives in the payload, so the retry can land on any instance and rebuild from the blob alone.</p>
<p><img decoding="async" loading="lazy" alt="Multi Round-Trip Requests sequence: server A answers a tool call with input_required plus a requestState blob, the client asks the user and retries, and the load balancer sends the retry to a different server B that rebuilds the state from the blob and completes." src="https://danduh.me/assets/images/mrtr-2e53c191da8dc4b938944a6985f0dce0.webp" width="1400" height="571" class="img_ev3q"></p>
<p>Server A asked the question. Server B finished the job. Neither of them held anything.</p>
<p>The <a href="https://modelcontextprotocol.io/seps/2322-MRTR" target="_blank" rel="noopener noreferrer">same pattern</a> swallowed URL elicitation too: no separate completion notification, no server-held <code>elicitationId</code>. The client finishes the external interaction and retries the original call. And there's a quietly good consequence — a server can no longer prompt you out of nowhere. Every question is causally tied to a call you started. Less spooky. Easier to audit.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="extensions-arent-bolt-ons-theyre-the-new-default-shape">Extensions aren't bolt-ons. They're the new default shape.<a href="https://danduh.me/blog/mcp-goes-stateless#extensions-arent-bolt-ons-theyre-the-new-default-shape" class="hash-link" aria-label="Direct link to Extensions aren't bolt-ons. They're the new default shape." title="Direct link to Extensions aren't bolt-ons. They're the new default shape.">​</a></h2>
<p>The stateless core is deliberately small, and everything that used to fight its way in now lives outside it as an extension. The mechanics are dull, and that's the point. Client and server each declare supported extensions as namespaced capabilities — official ones carry <code>io.modelcontextprotocol/*</code> identifiers, anyone else uses a reverse-DNS prefix so <code>com.acme/whatever</code> can't collide with the standard. Each extension versions and ships on its own clock, off the base-protocol calendar. So a new idea can land, get beaten on in production, and earn its way into core or not — without holding the whole spec hostage to one feature's release date.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="tasks-got-demoted-on-purpose">Tasks got demoted. On purpose.<a href="https://danduh.me/blog/mcp-goes-stateless#tasks-got-demoted-on-purpose" class="hash-link" aria-label="Direct link to Tasks got demoted. On purpose." title="Direct link to Tasks got demoted. On purpose.">​</a></h2>
<p>Tasks — long-running work you kick off and check on later — shipped as an experimental core feature in <code>2025-11-25</code>. Production got hold of it, found three things wrong, and the maintainers did the thing nobody wants to do to their own baby: they pulled it out of core and rebuilt it as <a href="https://modelcontextprotocol.io/seps/2663-tasks-extension" target="_blank" rel="noopener noreferrer">an extension</a>.</p>
<p>The old design made you prime a warmup before you could even issue a task. Its blocking <code>tasks/result</code> needed exactly the open SSE stream the new rules now ban. And <code>tasks/list</code> had no safe scope once the session was gone. The real reason, not the hand-wavy one: a random bearer handle with enough entropy can safely identify one task. It cannot safely enumerate every task a user is allowed to see. Enumeration needs an identity model — who are you, what may you list — and a protocol guessing game is not that. So <code>tasks/list</code> isn't replaced. It's removed.</p>
<p><img decoding="async" loading="lazy" alt="Tasks extension lifecycle: a tools/call returns a task handle in the working state, which can move to input_required and back via tasks/update, and terminates in completed, failed, or cancelled." src="https://danduh.me/assets/images/tasks-04fb5dfdd53dd1f81ad438b64bf3937e.webp" width="1400" height="718" class="img_ev3q"></p>
<p>What's left is smaller and reads clean. The client advertises the Tasks extension; the server decides per call whether to answer with a normal result or a task handle — kick off a full export of basket BBB and you get a handle back, not a hung connection. Reads (<code>tasks/get</code>) stay pure and idempotent. Writes (<code>tasks/update</code>, <code>tasks/cancel</code>) are separate. Call one against a server that never advertised the capability and you get a clean <code>-32003</code> from the extension, or <code>-32021</code> if you asked core. And the handle doesn't come back until the task is durably created — so you never poll for a task that isn't there yet.</p>
<p>The demotion is the real story, bigger than the feature. MCP now has a written path for "ship it as an extension, let production beat on it, promote it to core only if it survives." Tasks walked that path backwards and came out better for it. A protocol learning to say no to itself. Useful skill.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="ui-showed-up-and-its-in-a-box">UI showed up, and it's in a box<a href="https://danduh.me/blog/mcp-goes-stateless#ui-showed-up-and-its-in-a-box" class="hash-link" aria-label="Direct link to UI showed up, and it's in a box" title="Direct link to UI showed up, and it's in a box">​</a></h2>
<p>A tool can now render an actual interface. Not a wall of markdown — a real HTML app served under a <code>ui://</code> scheme, dropped into a sandboxed iframe with no access to your DOM, your cookies, or your storage, talking to the host only through <code>postMessage</code>. <a href="https://blog.modelcontextprotocol.io/posts/2026-01-26-mcp-apps/" target="_blank" rel="noopener noreferrer">MCP Apps</a> has four actors, and holding them apart is the trick: the Server publishes the tool and the UI resource, the Host is the MCP client and the policy boundary, the Model picks ordinary tools and reads ordinary results, and the View is the untrusted HTML in the sandbox.</p>
<p>The security property that matters is where a click goes. Hit a button in that View and it doesn't get a private tunnel to the server — it comes back as an ordinary <code>tools/call</code> through the host, down the identical path a call from the model would take: same authentication, same consent, same per-tool visibility check, same line in the audit log. Visibility defaults to <code>["model","app"]</code> per tool, and the host builds the sandbox's Content-Security-Policy out of <code>_meta.ui.csp</code>, so the server declares what its UI may load and the host enforces it. Server-authored code runs in a box, and everything it does leaves the box as structured JSON-RPC. That's the <a href="https://modelcontextprotocol.io/seps/1865-mcp-apps-interactive-user-interfaces-for-mcp" target="_blank" rel="noopener noreferrer">difference</a> between a UI standard you can ship to enterprises and remote code execution with a nicer name.</p>
<p>But let's be honest about what a sandbox can and can't do. It fences the code. It does not fence the story the code tells a human. A sandboxed iframe can still paint a login form that isn't one, or hide a destructive action behind a cheerful blue button. CSP stops the JavaScript from phoning home. It does nothing about a screenshot-perfect fake asking for your password. The host has to keep its own chrome visibly separate and always show where the pixels came from — because the one thing you cannot sandbox is a lie.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-auth-got-hardened-the-runtime-didnt">The auth got hardened. The runtime didn't.<a href="https://danduh.me/blog/mcp-goes-stateless#the-auth-got-hardened-the-runtime-didnt" class="hash-link" aria-label="Direct link to The auth got hardened. The runtime didn't." title="Direct link to The auth got hardened. The runtime didn't.">​</a></h2>
<p>Six changes tighten the <a href="https://modelcontextprotocol.io/specification/draft/basic/authorization" target="_blank" rel="noopener noreferrer">OAuth 2.1 profile</a>, and they're all the boring, correct kind. Issuer validation per RFC 9207 — the <code>iss</code> from the authorization response compared exactly against the issuer you picked before the redirect, no case folding, no trailing-slash cleanup. An <code>application_type</code> in dynamic registration, <code>native</code> for desktop and CLI and localhost clients, <code>web</code> for remote ones, so a desktop client's <code>localhost</code> redirect stops getting rejected on sight. Stored credentials bound to the validated issuer, so a resource moving issuer is a migration, not a quiet reuse of the old token. Defined refresh tokens, step-up scope accumulation, unambiguous <code>.well-known</code> paths, and a sane line on <code>offline_access</code> — the client may ask for it, the resource server should not demand it. Dynamic Client Registration is still there as a fallback but got deprecated late, in favour of Client ID Metadata Documents or pre-registration. And <a href="https://blog.modelcontextprotocol.io/posts/enterprise-managed-auth/" target="_blank" rel="noopener noreferrer">Enterprise-Managed Authorization</a> went stable on <code>2026-06-18</code> — your IdP decides whether a client may reach a server at all, Okta first, with Asana, Atlassian, Canva, Figma, Granola, Linear, and Supabase on board.</p>
<p>Real improvements. Take them.</p>
<p>But does any of it authorise a tool call? No. Every one of these six governs the handshake — who you are, which server you may reach, at what scope, once. Not one checks whether this specific call, right now, with these arguments, is allowed to run. Per-call authorisation — the thing you actually want when your agent is firing tools in a loop at 40 requests a second — is still entirely yours to build.</p>
<p>Six locks on the front door. Zero on the hallway.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-unglamorous-half-nobody-demos">The unglamorous half nobody demos<a href="https://danduh.me/blog/mcp-goes-stateless#the-unglamorous-half-nobody-demos" class="hash-link" aria-label="Direct link to The unglamorous half nobody demos" title="Direct link to The unglamorous half nobody demos">​</a></h2>
<p>Once every request stands alone, clients need to know when cached data has gone off. So <a href="https://modelcontextprotocol.io/specification/draft/server/utilities/caching" target="_blank" rel="noopener noreferrer">caching</a> grew up: <code>ttlMs</code> and <code>cacheScope</code> land on cacheable results — <code>public</code> reusable across auth contexts, <code>private</code> not, the cache key covering the method and every parameter that can move the result. The TTL is a freshness hint, not a promise and not a polling schedule. A matching change notification invalidates the entry the instant it arrives, and a broken stream does not make a stale entry immortal.</p>
<p>When it expires, fetch again. That's the whole contract.</p>
<p>The old general GET stream and <code>resources/subscribe</code>/<code>unsubscribe</code> folded into one <code>subscriptions/listen</code> — a filtered subscription the server acknowledges, then feeds only the notifications you asked for. No <code>Last-Event-ID</code> recovery: stream breaks, you retry with a new id. Correctness comes from refetching; notifications only shave latency. Closing an HTTP response stream now cancels that request cleanly, and long-lived subscription streams stay alive with SSE-comment keep-alives so a proxy doesn't guillotine them for going quiet.</p>
<p>The rest reads like a checklist written by someone who has been paged: W3C Trace Context rides through <code>_meta</code> so host, gateway, server, and every downstream call show up in one trace; the <a href="https://modelcontextprotocol.io/seps/2243-http-standardization" target="_blank" rel="noopener noreferrer"><code>Mcp-Method</code> and <code>Mcp-Name</code> headers</a> mirror the operation so a gateway can route, meter, and categorize traffic without parsing the body; and a tool input field marked <code>x-mcp-header</code> gets projected into an <code>Mcp-Param-*</code> header for the same reason. None of it makes a launch video.</p>
<p>A few quieter tightenings ride along. Tool schemas move to JSON Schema 2020-12, so what you already write for validation is what the protocol speaks. <code>structuredContent</code> stops insisting on an object — a tool can return an array, a number, a bare string, whatever the result actually is. Tool lists became deterministic — a stable ordering lets a client do a cheap equality check and lets the model's prompt cache actually hit. And the error codes got sorted: the old <code>-32002</code> resource-not-found folds into the standard <code>-32602</code>, and three new codes <code>-32020</code>/<code>-32021</code>/<code>-32022</code> show up. Not glamorous. Correct.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="who-actually-has-work-to-do">Who actually has work to do<a href="https://danduh.me/blog/mcp-goes-stateless#who-actually-has-work-to-do" class="hash-link" aria-label="Direct link to Who actually has work to do" title="Direct link to Who actually has work to do">​</a></h2>
<p>Ok, and now the part nobody demos… let's sort the migration by how much it's going to hurt. It is genuinely not the same for everyone.</p>
<ul>
<li>stdio-only local server, no HTTP sessions → wait for your Tier-1 SDK's release candidate — Python, TypeScript, Go, or C# — bump the dependency, done.</li>
<li>Behind a gateway or a load balancer → drop the session id, move per-connection state onto explicit handles or the authenticated user, implement <code>server/discover</code>, emit <code>Mcp-Method</code> and <code>Mcp-Name</code>, and confirm your CDN and WAF pass <code>Mcp-*</code> through untouched — a proxy that strips an unknown header fails every request and looks exactly like a client bug at 2am.</li>
<li>Built on the experimental Tasks API, or on server-initiated SSE elicitation → budget real time.</li>
</ul>
<p>stdio-only local server = a no-op. Behind a gateway = the actual work, all of that 0.7% doing sticky routing and then some. Built on experimental Tasks = a rewrite.</p>
<p>And the trap in all three: don't migrate by chasing compiler errors. Detect the era explicitly and keep legacy and modern state on separate paths — an old client wants initialization and a session, a new one sends self-describing calls, an old server omits <code>resultType</code> and a new one must include it. Blend both eras in one ambiguous code path and you get bugs that look like networking, then capability negotiation, then random server amnesia. Test the wire, not the TypeScript types.</p>
<p>And don't build off the announcement examples. The RC was frozen back in May, but the intended-final draft kept moving: <code>clientInfo</code> became optional, <code>serverInfo</code> moved into result metadata, URL elicitation lost its completion notification, error ranges shifted, and DCR got deprecated late. The candidate stood still. The design did not.</p>
<p>Nothing forces your hand on the date. Old clients and new servers keep talking by <a href="https://modelcontextprotocol.io/docs/learn/versioning" target="_blank" rel="noopener noreferrer">negotiating down to <code>2025-11-25</code></a> — that's the whole back-compat story for the deleted pieces. Version negotiation, not a grace period. The stateless removals are a clean break, and none of them got the twelve-month deprecation floor: that floor covers features carrying the Deprecated annotation that still work, not calls cut outright. You migrate when your SDK is ready, not when the calendar tells you to.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-part-where-a-protocol-grows-up">The part where a protocol grows up<a href="https://danduh.me/blog/mcp-goes-stateless#the-part-where-a-protocol-grows-up" class="hash-link" aria-label="Direct link to The part where a protocol grows up" title="Direct link to The part where a protocol grows up">​</a></h2>
<p>There's one more change behind the mechanics, and it outlives all of them: MCP now has a grown-up way to change. Substantial proposals go through the SEP process with a Working Group attached, and nothing on the Standards Track reaches Final without a conformance-suite scenario proving it behaves. Features get real lifecycle states — Active, Deprecated, Removed — with a twelve-month floor before a deprecation is even eligible to be pulled, and a 90-day fast lane for security. <a href="https://modelcontextprotocol.io/seps/2577-deprecate-roots-sampling-and-logging" target="_blank" rel="noopener noreferrer">Roots, Sampling, and Logging</a> are the first residents of Deprecated; they still work, they just carry the annotation now.</p>
<p>There's a wonderful bit of comedy in the timing, the deadpan kind. This is the revision that writes down the careful twelve-month, conformance-gated, don't-yank-things-out-from-under-people policy — in the same breath that it yanks the session, the handshake, the GET stream, and half a dozen calls out of the core. Fine. You usually write the safety manual right after the thing you wish you'd had a safety manual for.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="so-is-this-mcp-20">So, is this MCP 2.0?<a href="https://danduh.me/blog/mcp-goes-stateless#so-is-this-mcp-20" class="hash-link" aria-label="Direct link to So, is this MCP 2.0?" title="Direct link to So, is this MCP 2.0?">​</a></h2>
<p>Officially, no. It's still date-versioned, it's <code>2026-07-28</code>, and the maintainers will tell you the "2.0" label is a marketing accident.</p>
<p>Conceptually? I get why it sticks. The protocol just dropped its founding assumption — that a connection remembers who you are and what happened before you got here. Now every request explains itself, state carries a name instead of hiding in the wire, a paused interaction is a retry instead of a private phone call. Sessions felt like maturity. Real servers hold state, right? Turns out the session was the one grown-up-sounding thing standing between your server and a load balancer you never have to think about again. yeah, the maintainers looked at the most impressive feature they owned and hit delete.</p>
<p>State was free the whole time. The session was just the subscription.</p>]]></content:encoded>
            <category>MCP</category>
            <category>Architecture</category>
            <category>Agents</category>
        </item>
        <item>
            <title><![CDATA[You Can't Make Your Company AI Native Without Dealing With This First]]></title>
            <link>https://danduh.me/blog/ai-native-knowledge</link>
            <guid>https://danduh.me/blog/ai-native-knowledge</guid>
            <pubDate>Sun, 12 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Everyone's talking about becoming an AI native company. Almost everyone misses the one thing that decides whether it works — whether your organizational knowledge is structured, trusted, and accessible to AI. Here's how to fix it.]]></description>
            <content:encoded><![CDATA[
<div style="margin-bottom:1.25rem"><img src="https://danduh.me/img/ai-native-knowledge/main_image-1200.webp" srcset="https://danduh.me/img/ai-native-knowledge/main_image-800.webp 800w, https://danduh.me/img/ai-native-knowledge/main_image-1200.webp 1200w, https://danduh.me/img/ai-native-knowledge/main_image.webp 1536w" sizes="(max-width: 996px) 100vw, 768px" width="1536" height="1024" alt="A confused robot standing among toppled Confluence, Jira, Slack, and Notion blocks amid error messages — the reality of &quot;going AI native&quot; on top of scattered knowledge." loading="eager" fetchpriority="high" style="display:block;width:100%;height:auto"></div>
<p>Ok, let's say you working in the company and your management or you the manager all day long talking about being AI native. First of all what does it mean to become an AI native company? Fire all developers and build AI agents to make all work? Maybe. Or something less radical, like add AI in all steps of product lifecycle? Might be.</p>
<p>Regardless how radical you want to be, probably you will miss the most important point, without it all your idea of becoming an AI native company will fail. Moreover you might damage your company badly.</p>
<!-- -->
<p>Let's go over a basic product life cycle (I believe it will cover up to 80% of tech companies out there).</p>
<p><img decoding="async" loading="lazy" alt="A basic product lifecycle — Discovery, Definition, Design, Build, Launch, Growth, Maturity, Decline/Pivot — with the internal actors mapped beneath each stage." src="https://danduh.me/assets/images/sdlc_diagram-bf29dbe6d2344b0346521cbf71ecee22.webp" width="2000" height="715" class="img_ev3q"></p>
<p>Let's define Actors:</p>
<ul>
<li><strong>Product Manager</strong> — comes up with ideas for new features, owns the roadmap, and makes prioritisation calls across the entire lifecycle</li>
<li><strong>Product Analyst</strong> — tells which features are in use, identifies patterns in user behaviour, and suggests new features or improvements to Product</li>
<li><strong>UX Designer</strong> — translates product ideas into wireframes, prototypes and user flows; ensures the experience is usable before a line of code is written</li>
<li><strong>Architect</strong> — defines the technical foundation; makes system design decisions at definition and design phases to ensure the product can scale, integrate, and be maintained long-term</li>
<li><strong>Engineering</strong> — builds the product; turns specs and designs into working software, raises technical constraints early</li>
<li><strong>QA / Testing</strong> — validates that what Engineering built matches what was defined; catches regressions and edge cases before release</li>
<li><strong>Marketing</strong> — shapes how the product is positioned and communicated to the market; drives awareness at launch and ongoing growth</li>
<li><strong>Sales</strong> — converts market interest into revenue; feeds back real customer objections and needs into the roadmap</li>
<li><strong>Customer Support</strong> — first line of contact after launch; surfaces recurring pain points and bugs that inform future iterations</li>
<li><strong>Data / Analytics</strong> — measures everything post-launch; tells the business what is working, what is not, and where to invest next</li>
<li><strong>Finance</strong> — validates the business case, tracks ROI, and gates investment decisions at key lifecycle milestones</li>
<li><strong>Legal / Compliance</strong> — ensures the product meets regulatory requirements; involved at definition and before any major release</li>
<li><strong>Exec / Leadership</strong> — sets strategic direction, allocates resources, and makes go/no-go calls at critical lifecycle gates</li>
</ul>
<p><img decoding="async" loading="lazy" alt="The actors in product development, each holding the tool of their trade — from the PM&amp;#39;s lightbulb to Finance&amp;#39;s coins." src="https://danduh.me/assets/images/actors_in_product_development-107bb55ec9573446a9ef2ca0cc9b3507.webp" width="2000" height="1091" class="img_ev3q"></p>
<p>Ok. Up until now nothing new… let's add something that connects all of them… can you guess what is this? Yeah, money.. But I'm talking about something else — knowledge. How do all those actors share information? Meetings, emails, chats? Let's have another look at knowledge storages of different types.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="where-does-all-the-knowledge-live-spoiler-everywhere-and-nowhere">Where does all the knowledge live? (Spoiler: everywhere and nowhere)<a href="https://danduh.me/blog/ai-native-knowledge#where-does-all-the-knowledge-live-spoiler-everywhere-and-nowhere" class="hash-link" aria-label="Direct link to Where does all the knowledge live? (Spoiler: everywhere and nowhere)" title="Direct link to Where does all the knowledge live? (Spoiler: everywhere and nowhere)">​</a></h2>
<p>Let's talk about something nobody warns you about when you join a product team. Every actor in the product lifecycle has their own favourite place to store knowledge. And surprise — none of them talk to each other by default.</p>
<p>You end up with a beautiful mess. The product manager has the grand vision locked in Confluence. The analyst has the real data somewhere in a Notion doc nobody can find. The architect drew the perfect diagram in Miro at 11pm and never shared the link. Developers have the actual truth buried in Jira tickets and Git commits. And the customer? They screamed into the Zendesk void and nobody connected that feedback to anything.</p>
<p>Here's how it actually breaks down by actor.</p>
<p><strong>Product Manager</strong> lives in Confluence, Notion, or ProductBoard. This is where the PRDs, roadmaps, and meeting notes go to slowly become outdated. ProductBoard is for collecting ideas and prioritising features — basically a graveyard of great ideas that "aren't in scope this quarter."</p>
<p><strong>Product Analyst</strong> lives in Mixpanel, Amplitude, Looker, or Google Analytics. They know which features nobody uses. They just need someone to actually read their reports.</p>
<p><strong>UX Designer</strong> works in Figma and documents things in Confluence or Notion. Figma is where the real truth lives — the design system, the flows, the components. Confluence is where they paste screenshots of Figma that are already outdated.</p>
<p><strong>Architect</strong> owns Miro, Lucidchart, or draw.io for diagrams, and drops the technical specs into Confluence. The architecture doc is usually either 3 years old or written last week in a panic before a review.</p>
<p><strong>Engineering</strong> lives in Jira (tickets), GitHub/GitLab (code and PRs), and Confluence (technical docs nobody updates). The real knowledge is in the code and in developers' heads. That's the scary part.</p>
<p><strong>QA / Testing</strong> tracks everything in Jira, TestRail, or Zephyr. Bug reports, test cases, regression results. It's detailed, organised, and the last place a product manager will ever look.</p>
<p><strong>Marketing</strong> keeps their stuff in HubSpot, Notion, or shared Google Docs. Campaign briefs, messaging guidelines, competitor research — living in folders with names like "Final_v3_ACTUAL_final."</p>
<p><strong>Sales</strong> runs on Salesforce or HubSpot CRM. Call notes, deals, customer objections, win/loss reasons. Probably the richest source of real-world product feedback. Also probably the least connected to the product team.</p>
<p><strong>Customer Support</strong> works in Zendesk, Intercom, or Freshdesk. Every complaint, every workaround, every "this is broken" message from real users is sitting there. Waiting. Mostly ignored by product until something breaks badly enough.</p>
<p><strong>Data / Analytics</strong> stores everything in Snowflake, BigQuery, dbt, and visualises in Tableau, Looker, or Metabase. They have answers to questions nobody has asked yet.</p>
<p><strong>Finance</strong> runs on Excel. Always. Maybe SAP or NetSuite if the company is grown up. Budget models, ROI forecasts, headcount plans — in spreadsheets with 47 tabs.</p>
<p><strong>Legal / Compliance</strong> uses SharePoint, internal wikis, or just… email threads. Contracts, compliance docs, policy guidelines live here. Usually in a folder structure designed in 2014.</p>
<p><strong>Exec / Leadership</strong> consumes knowledge through dashboards (Tableau, Looker), slide decks (PowerPoint, Google Slides), and Slack. They don't go looking for knowledge — knowledge needs to come to them, formatted nicely, in under 5 slides.</p>
<p><strong>Customers</strong> leave their knowledge in Zendesk tickets, App Store reviews, Typeform surveys, NPS responses, and UserVoice requests. This is the most honest knowledge in the whole organisation. It also has the longest journey to actually influence anything.</p>
<p>The brutal reality is this: 36% of organisations use three or more knowledge management tools, and 31% aren't even sure how many tools they have in place. Most knowledge doesn't flow — it pools. Each team builds their own lake, and crossing between them requires either a very motivated person or a very painful incident.</p>
<p><img decoding="async" loading="lazy" alt="Where the knowledge lives — each actor&amp;#39;s favourite tool rebranded by its dysfunction: ProductBoard the idea graveyard, Jira the ticket vortex, Excel&amp;#39;s 47 tabs, SharePoint&amp;#39;s 2014 folders." src="https://danduh.me/assets/images/knowledge_diagram-986c375a3586d89945771626dfbf7686.webp" width="2000" height="715" class="img_ev3q"></p>
<p>And here's the uncomfortable truth that the diagram politely hides: most of these tools don't talk to each other. The product manager writes a PRD in Confluence. The engineer closes the Jira ticket. The sales rep logs the customer objection in Salesforce. The analyst sees the drop in Amplitude. And nobody connects those four dots — because there is no system that does it automatically. Someone has to care enough to do it manually. That someone is usually called "the one person who actually reads everything," and they're always slightly burned out.</p>
<p>The tools are fine. The connections between them — that's where knowledge goes to die.</p>
<p>Ok, and now let's talk about being AI native…</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="section-3--so-you-want-ai-to-help-lets-see-what-its-actually-working-with">Section 3 — So you want AI to help. Let's see what it's actually working with.<a href="https://danduh.me/blog/ai-native-knowledge#section-3--so-you-want-ai-to-help-lets-see-what-its-actually-working-with" class="hash-link" aria-label="Direct link to Section 3 — So you want AI to help. Let's see what it's actually working with." title="Direct link to Section 3 — So you want AI to help. Let's see what it's actually working with.">​</a></h2>
<p>Here is where it gets interesting. And by interesting I mean a little painful.</p>
<p>Let's take a developer. Modern, senior, not afraid of AI tools. He opens Cursor or uses Claude Code, connects MCP to Jira, pulls the ticket, connects MCP to GitHub, creates the PR. Doesn't even open IDE. Impressive, right? He's telling everyone in the Slack channel he's fully AI native now.</p>
<p>But wait. What happens when the ticket says "implement feature BBB according to the architecture"? AI goes to find the architecture. Where is it? In Miro. Does Cursor have MCP for Miro? Maybe. Does the diagram have enough context written in it or it's just boxes and arrows that only the architect who drew it at midnight understands? Almost certainly the second option.</p>
<p>So the developer copies the diagram, pastes it into Claude. Then remembers there's an HLD somewhere in Confluence. Finds it. It's from 2022. Pastes it anyway. Then the product requirement mentions a shared library. Developer goes to find the README. It's three lines long and the last commit was 14 months ago. Pastes it too.</p>
<p>Now Claude has a context window full of contradictory, outdated, and half-finished information. And here's the thing — even if you have a model with a million token context window, more tokens doesn't mean better results. Research measured 18 different LLMs and found that "models do not use their context uniformly — performance grows increasingly unreliable as input length grows." Basically you're not giving AI more knowledge, you're giving it more noise to get lost in.</p>
<p>There's even a name for this now. Context rot. When an agent lacks good institutional knowledge, it enters a vicious cycle — guess, fail, get corrected, retry. Each iteration fills the context window with more garbage and degrades reasoning further. The DORA 2025 report documented this across the industry: despite 90% AI tool adoption, there was no clear link between adoption and reduction in developer friction or burnout. Developers went faster individually but teams didn't deliver faster.</p>
<p>And that's the developer case. A relatively structured job with tickets and PRs and code.</p>
<p>Now let's look at the architect. An architect needs to design a system for feature BBB. To do this properly, he needs to know: what systems already exist, what their limitations are, what shared libraries are available, what the compliance requirements are, what similar decisions were made before and why, what the team owning Service X can actually support, and what the data model looks like today versus what the PRD assumes. That's at least six different sources of truth. Some of them are in Confluence. Some are in someone's head. Some of the Confluence pages are outdated. Some of the heads have left the company.</p>
<p>Can AI help here? Absolutely. But only if all this information is actually accessible, structured, and trustworthy. If you ask AI to design an architecture based on garbage inputs, you get a beautiful, well-written, completely wrong architecture. And then engineers build it. And then you spend six months wondering why it doesn't work.</p>
<p>So no. Just having AI tools is not being AI native. You're just spending more on tokens while doing the same amount of manual context management as before.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="section-4--half-the-problem-cleaning-what-you-already-have">Section 4 — Half the problem: cleaning what you already have<a href="https://danduh.me/blog/ai-native-knowledge#section-4--half-the-problem-cleaning-what-you-already-have" class="hash-link" aria-label="Direct link to Section 4 — Half the problem: cleaning what you already have" title="Direct link to Section 4 — Half the problem: cleaning what you already have">​</a></h2>
<p><img decoding="async" loading="lazy" alt="Scattered floating islands of knowledge — analytics, docs, architecture, code, archives — each isolated from the others." src="https://danduh.me/assets/images/Half_the_problem-cleaning_what_you_already_have-a96124d7ed520ca39daab113c0162a72.webp" width="2000" height="1091" class="img_ev3q"></p>
<p>Here's the uncomfortable reality. Before you can think about making AI work with your knowledge, you need to deal with the knowledge you already have. And most of it is, let's be honest, a mess.</p>
<p>I've worked in more than 15 companies over 25 years. Startups, enterprises, everything in between. The one constant is that everyone has a knowledge management problem. Everyone knows it. Everyone has tried to fix it at least twice. And somehow there are always five tools running in parallel, three Confluence spaces nobody maintains, and a Wiki page titled "Current Architecture" that describes a system that was replaced in 2021.</p>
<p>So before you index anything into your shiny new knowledge graph, you need to sort what's actually worth indexing. Here's how I think about it.</p>
<p><strong>Production code = ground truth.</strong> If a service is running in production right now, that's reality. That's a 100 trust score. Whatever documentation says about it doesn't matter — the code is what it is. If the docs say the service uses PostgreSQL and the code says MongoDB, the code wins. Always.</p>
<p><strong>Closed tickets (epics and stories) = high trust.</strong> A ticket that went through definition, development, review, and was marked done — that's a record of something that actually happened. Not bugs, not subtasks, those are too granular and noisy. But a properly closed epic or story is a meaningful, trustworthy unit of organizational knowledge.</p>
<p><strong>Passing test plans = medium trust.</strong> Tests that run regularly and consistently pass reflect how the system actually behaves. Tests that are skipped, failing, or haven't run in months? Those tell you something too — but not what the system does. They tell you what someone hoped the system would do at some point.</p>
<p><strong>Wiki and Confluence pages = verify before trusting.</strong> This is where it gets messy. A wiki page has no inherent trust signal. It might have been written yesterday by a senior engineer who just redesigned the whole system. Or it might have been written in 2019 by an intern who left a week later. You can't tell from the content alone.</p>
<p>How do you filter? A few signals help. When was it last edited, and by whom? Does it reference services or features that still exist? Are there links in it that still work? Does the code referenced in it actually match what's in the repo? These aren't perfect filters but they're good enough to categorize a page as probably current, probably stale, or definitely dead.</p>
<p>Here's a practical heuristic: if a Confluence page hasn't been touched in 18 months and describes something that should change more often than that — like an API contract, a deployment process, or a data model — treat it as stale by default. Require a human to verify before it gets indexed.</p>
<p><strong>Figma files = almost unindexable without effort.</strong> Most Figma links in PRDs or epics have no description next to them. Just a URL. So you have a link that points to a specific frame, in a file that may have been renamed, in a team that may have reorganized. And even if you can access the content, how do you know which version is current? Design files have histories, branches, drafts. Without explicit ownership and a "this is the approved version" marker, Figma is just expensive noise in your knowledge graph.</p>
<p>The only way Figma becomes trustworthy knowledge is if teams adopt a discipline: final, approved designs get exported as described components into a system that can be indexed. Not a raw Figma link. A summary, a component name, a version. It's more work. But without it, you're indexing "someone drew something" and that's not knowledge.</p>
<p><strong>README files in shared libraries = depends.</strong> If the library has active users, recent commits, and the README was updated in the last release — good signal. If it's a shared library from 2018 that "everyone uses" but nobody touches — that README is documentation for a black box, not for a living system. Treat accordingly.</p>
<p>The hard truth about filtering. You can't fully automate this. You can score content automatically based on age, link health, git activity correlation, and deployment records. But the final call on "is this still true" often requires a human who understands what this piece of the system actually does today. The good news is you only have to do the full audit once. After that, if you build good processes for new knowledge (more on this below), you don't let the mess accumulate again.</p>
<p>The goal of this whole exercise is not to have a perfect knowledge base. That doesn't exist. The goal is to have a knowledge base where AI can say with reasonable confidence "this is probably accurate" versus "this might be outdated, treat with caution." That distinction matters enormously when AI is making architectural recommendations or writing code.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="section-5--the-other-half-building-new-knowledge-correctly">Section 5 — The other half: building new knowledge correctly<a href="https://danduh.me/blog/ai-native-knowledge#section-5--the-other-half-building-new-knowledge-correctly" class="hash-link" aria-label="Direct link to Section 5 — The other half: building new knowledge correctly" title="Direct link to Section 5 — The other half: building new knowledge correctly">​</a></h2>
<p>Cleaning the old stuff is hard. Building new knowledge correctly from the start is, surprisingly, harder. Because it requires people to change behavior. And people don't love changing behavior.</p>
<p>Let's follow feature BBB through its lifecycle and ask the simple question: when does something become true?</p>
<p>The product manager writes a PRD. Is that truth? No. It's an intention. A hypothesis. It might change three times before anyone writes a line of code. Indexing it as ground truth at this stage would pollute your knowledge graph with wishes.</p>
<p>The PRD gets approved. Is it truth now? Getting warmer. It's a committed intention. But the implementation might still diverge. Things get discovered. APIs turn out to have different limitations than expected. The design gets revised. The scope gets cut.</p>
<p>The feature gets built and deployed to staging. Is the HLD truth now? Partially. The high-level design reflects something real but the details are still in flux. API contracts might still be changing. Edge cases are being discovered.</p>
<p>The feature goes to production. Now we're talking. The code is truth. The closed epic is truth. The passing tests are truth. The architecture as-implemented is truth — not the HLD as written, but the HLD corrected by whatever actually got built.</p>
<p>So the right answer to "when should we index this" is not a single moment. It's a staged process:</p>
<ul>
<li>PRD approved → index as intention with low trust score, tagged "in progress"</li>
<li>Development starts → HLD and architecture docs indexed as design, medium trust, mutable</li>
<li>Feature released to production → all related knowledge promoted to verified, high trust, code becomes the arbiter of any conflicts</li>
<li>Six months in production with no major issues → treated as stable knowledge, used confidently in future architecture decisions</li>
</ul>
<p>This sounds obvious when you write it down. But almost no company does it. They either index everything all the time (chaos) or nothing (ignorance). The middle path — staged trust elevation — is what actually works.</p>
<p>The other critical part of building new knowledge is making it someone's responsibility. Not "everyone's responsibility." Everyone's responsibility is nobody's responsibility. You need someone — a role, a ritual, a checkpoint — that ensures knowledge gets captured correctly at each lifecycle gate.</p>
<p>In practice this looks like: no epic closes unless the ADR is written. No architecture review sign-off unless the HLD is updated to reflect what was actually decided. No production release unless the README of any affected library is updated. These aren't bureaucratic checkboxes. They're the price of admission for AI native operations. If you skip them, you're back to copy-pasting context by hand.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="section-6--the-temp-memory-idea-and-why-its-actually-the-most-interesting-part">Section 6 — The temp memory idea, and why it's actually the most interesting part<a href="https://danduh.me/blog/ai-native-knowledge#section-6--the-temp-memory-idea-and-why-its-actually-the-most-interesting-part" class="hash-link" aria-label="Direct link to Section 6 — The temp memory idea, and why it's actually the most interesting part" title="Direct link to Section 6 — The temp memory idea, and why it's actually the most interesting part">​</a></h2>
<p><img decoding="async" loading="lazy" alt="Official flow versus real flow — a clean linear diagram beside a tangle of crossing arrows and a question mark." src="https://danduh.me/assets/images/Section_6-fd439d7d2b347182d61badc1e36f7ab7.webp" width="2000" height="1091" class="img_ev3q"></p>
<p>Ok so here's the idea I've been thinking about and I haven't seen anyone describe it quite this way.</p>
<p>What if a feature had its own memory?</p>
<p>Not a document. Not a Jira epic. A living, append-only knowledge context that follows feature BBB from the moment it's conceived to the moment it ships — and then gets promoted into the permanent knowledge graph.</p>
<p>Think of it like a construction site. When you're building, you have temporary scaffolding, work-in-progress materials, partially completed structures. You don't put scaffolding in the building's official blueprints. But the scaffolding is real and necessary while you're building. When construction is done, you take down the scaffolding, document the actual building, and file the final blueprints.</p>
<p>Feature memory works the same way. From day one of feature BBB, there's a dedicated knowledge container. Every relevant artifact gets appended to it:</p>
<ul>
<li>PRD created → appended</li>
<li>Architecture diagrams → appended with links and versions</li>
<li>ADR written → appended</li>
<li>API contract defined → appended</li>
<li>Compliance check done → appended with outcome</li>
<li>Test plan created → appended</li>
<li>PRD updated (version 2) → appended, old version marked superseded</li>
<li>API contract changed during development → appended, old version marked superseded</li>
</ul>
<p>The key word is appended. Nothing gets deleted. Everything gets timestamped. The memory always knows what was true when, and what superseded what. This is temporal knowledge — not just "what is the API contract" but "what was the API contract on March 15th when we made this architectural decision."</p>
<p>There's actually a real framework that works exactly like this. Graphiti, built by Zep AI, implements temporally-aware knowledge graphs where facts have validity windows. When something changes, old facts are invalidated — not deleted — and the new fact takes over. You can query what's true now, or what was true at any point in time. That's exactly what feature memory needs.</p>
<p>When feature BBB ships to production, the temp memory doesn't disappear. It gets promoted. A summarization pass runs — probably AI-assisted — that distills the key decisions, the final architecture, the API contracts as-implemented, and the lessons learned. This summary goes into the main knowledge graph as a verified, high-trust node. The full history stays accessible if you need to trace why a decision was made, but the promoted summary is what future architects and developers query.</p>
<p>Now the sync question. This is the hard part and I'm not going to pretend it's solved.</p>
<p>If the PRD lives in Confluence and the product manager updates it, how does the feature memory know? If the Figma design gets revised, how does the memory update? If a developer changes an API contract in code, how does that propagate back?</p>
<p>There are a few approaches and none of them is perfect:</p>
<p><strong>Webhook-based sync.</strong> Every time a relevant document changes in Confluence, Jira, GitHub, Figma — a webhook fires, the change gets appended to the feature memory. Works in theory. Requires every tool to support webhooks (most do), requires someone to set up the integrations, and requires good document-to-feature tagging so the webhook knows which feature memory to update.</p>
<p><strong>Scheduled polling with change detection.</strong> A background process checks for changes in linked documents every few hours. Less real-time but simpler to implement. Good enough for documents that don't change frequently, like architecture docs.</p>
<p><strong>Human checkpoints.</strong> At each lifecycle gate (PRD approval, architecture review, staging release, production release), a checklist requires the relevant actor to confirm "all changes are reflected in the feature memory." Manual, but forces accountability.</p>
<p><strong>AI-assisted monitoring.</strong> An AI agent watches for semantic changes in source documents and flags them. "The API contract in Confluence now differs from what's indexed in feature BBB memory — please review." This is the most interesting option and it's technically feasible today with the right tooling. It's also the one nobody has fully shipped in a production company context yet.</p>
<p>My honest take: start with webhooks plus human checkpoints at gates. Don't try to automate everything on day one. The discipline of "before we close this epic, let's verify feature memory is current" is more valuable than perfect automation that nobody trusts.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="section-7--so-what-does-ai-native-actually-look-like">Section 7 — So what does AI native actually look like?<a href="https://danduh.me/blog/ai-native-knowledge#section-7--so-what-does-ai-native-actually-look-like" class="hash-link" aria-label="Direct link to Section 7 — So what does AI native actually look like?" title="Direct link to Section 7 — So what does AI native actually look like?">​</a></h2>
<p>Let's close with the question we started with. What does it actually mean to be AI native?</p>
<p>It's not using Cursor. It's not having Claude write your PRDs. It's not building agents for every department. Those are tools. Tools don't make you AI native.</p>
<p>AI native means your knowledge is structured, trusted, and accessible — to AI — at every step of your product lifecycle. Without a human manually bridging the gap.</p>
<p><img decoding="async" loading="lazy" alt="A laptop wired into a glowing knowledge graph in a data center — knowledge structured and accessible to AI at every step." src="https://danduh.me/assets/images/Section_7-89ec959b38213b1dc28acf96b5d1c156.webp" width="2000" height="1116" class="img_ev3q"></p>
<p>Here's what that actually looks like in practice:</p>
<p>When a product manager starts working on feature BBB, they spin up a feature knowledge context. Every artifact they create — research, competitive analysis, PRD — gets indexed immediately, tagged, and linked to relevant existing knowledge in the graph. AI helps them at this stage by surfacing related past features, relevant compliance constraints, and similar architectural decisions the company has made before. Not because someone briefed the AI. Because the knowledge is there.</p>
<p>When the architect starts the HLD, they don't start from a blank page. They query the knowledge graph for the current state of the affected systems, the relevant ADRs, the shared libraries, the team boundaries. AI generates a first-draft HLD that's actually grounded in reality. The architect's job is to review, correct, and approve — not to gather information manually for three days.</p>
<p>When the developer picks up a ticket, their AI agent already has the full feature context. PRD, HLD, API contracts, relevant test cases, coding conventions for this codebase. The developer describes what they want to build. The agent builds it with full awareness of how it fits into the system. No copy-pasting. No "let me find that Confluence page."</p>
<p>When QA runs tests, the results feed back into the feature memory automatically. Failing tests are flagged. The feature memory now contains not just what was intended but what was verified.</p>
<p>When the feature ships to production, the knowledge graph gets updated. The promoted summary is there for the next person who needs to build something on top of this. The architectural decisions are recorded. The trade-offs are documented. The lessons learned are captured.</p>
<p>And six months later, when a new developer joins the team and asks "how does feature BBB work and why was it built this way" — the answer exists. Not in someone's head. In the graph.</p>
<p>That's AI native.</p>
<p>Is this utopia? A little bit. But here's the thing — none of this requires technology that doesn't exist. GraphRAG is real. Temporal knowledge graphs are real. Webhook integrations are real. The technology is ready. What's missing in most companies is the discipline, the process design, and the cultural understanding that knowledge management is not a nice-to-have. It's the foundation everything else is built on.</p>
<p>You can spend a million dollars on AI tools. You can give every developer a Cursor subscription and every product manager a Claude account. And if your knowledge is siloed, outdated, and inaccessible to AI — you'll get faster individual contributors who still work in an organisationally slow company. You'll have AI-assisted outputs built on garbage inputs.</p>
<p>The companies that figure this out first — the ones that treat their knowledge graph as a first-class engineering concern, that build discipline around it, that make it the connective tissue between every actor and every lifecycle stage — those are the ones that will actually benefit from AI at scale.</p>
<p>Everyone else is just buying expensive autocomplete.</p>]]></content:encoded>
            <category>AI</category>
            <category>Architecture</category>
            <category>GraphRAG</category>
        </item>
        <item>
            <title><![CDATA[AI Is Building Code Nobody Can Maintain. You're Next.]]></title>
            <link>https://danduh.me/blog/ai-amnesia-graphrag</link>
            <guid>https://danduh.me/blog/ai-amnesia-graphrag</guid>
            <pubDate>Mon, 05 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Organizations ship code faster than ever — and forget faster than ever. How a GraphRAG-backed knowledge graph gives your org (and your AI agents) real long-term memory.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="AI amnesia — a glowing brain wired to PRD, ADR, PR, release, and incident nodes." src="https://danduh.me/assets/images/ai-amnesia-cfbc1089bc00e60f71e7571bd40a1723.webp" width="720" height="488" class="img_ev3q"></p>
<p>Organizations are shipping code faster than ever. They're also forgetting faster than ever. AI agents write features in hours, but six months later, no one can explain why the system looks the way it does. The code works. The tests pass. But the story is gone.</p>
<!-- -->
<!-- -->
<blockquote>
<p><em>Originally published on <a href="https://medium.com/@danduh/ai-is-building-code-nobody-can-maintain-youre-next-141fdbfc0b2b" target="_blank" rel="noopener noreferrer">Medium</a>.</em></p>
</blockquote>
<p>It came up in a weekly review. The VP of Engineering asked a simple question:</p>
<blockquote>
<p>"Who owns <code>billing-orchestrator-service</code>?"</p>
</blockquote>
<p>Silence. People glanced at each other. Someone dug through Jira, then GitHub — and found only commits labeled <code>agent-generated-2024-05-12</code>. Eventually an engineer said it out loud:</p>
<blockquote>
<p>"The AI built most of that."</p>
</blockquote>
<p>The service that moved money in and out of the company didn't have a clear owner, a clear history, or a clear story. It existed. It mostly worked. But no one could explain why it looked the way it did.</p>
<p>That's <strong>AI amnesia</strong>.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="ai-didnt-just-replace-developers--it-replaced-memory">AI Didn't Just Replace Developers — It Replaced Memory<a href="https://danduh.me/blog/ai-amnesia-graphrag#ai-didnt-just-replace-developers--it-replaced-memory" class="hash-link" aria-label="Direct link to AI Didn't Just Replace Developers — It Replaced Memory" title="Direct link to AI Didn't Just Replace Developers — It Replaced Memory">​</a></h2>
<p>From outside, it looks simple: "AI is replacing developers." Up close, it's messier.</p>
<p>Companies lean into automation. AI agents write code. AI handles support. AI makes decisions. The first few months look great. Charts go up, tickets close faster, slides get shinier.</p>
<p><strong>Then reality hits.</strong></p>
<p>Take <strong>Klarna</strong>. They pushed hard into AI customer support. The CEO celebrated replacing people's jobs. Then customers with complex issues got stuck in chatbot loops. Nuance turned into canned answers. Frustration grew.</p>
<p>The story changed fast: "we're making sure you can reach a human." Employees got reassigned back to support.</p>
<p><strong>Google</strong> laid off engineers. Then they needed them back. As the AI race heated up, they rehired "boomerang" employees — people they'd let go — for their knowledge of systems and culture.</p>
<p>There's that viral founder: "I fired my dev team and replaced them with AI." Internet fame followed. Months later, he was hiring developers on LinkedIn.</p>
<p>Call it what you want. The pattern is clear: code without understanding is a time bomb.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="these-organizations-underestimated-human-memory">These Organizations Underestimated Human Memory<a href="https://danduh.me/blog/ai-amnesia-graphrag#these-organizations-underestimated-human-memory" class="hash-link" aria-label="Direct link to These Organizations Underestimated Human Memory" title="Direct link to These Organizations Underestimated Human Memory">​</a></h2>
<p><img decoding="async" loading="lazy" alt="A humanoid agent between a pile of fragmented, agent-generated code and a connected knowledge graph of PRDs, ADRs, incidents, and history." src="https://danduh.me/assets/images/organization_human_memory-04d5148497326fc97027da106cf373db.webp" width="720" height="480" class="img_ev3q"></p>
<p>AI generates outputs. But those outputs don't come with intent, rationale, or shared understanding. You still need someone who can answer:</p>
<ul>
<li>Why is it built this way?</li>
<li>What did we try before?</li>
<li>What must never happen again?</li>
</ul>
<p>When those answers evaporate, you lose the ability to move.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-same-pattern-in-engineering">The Same Pattern in Engineering<a href="https://danduh.me/blog/ai-amnesia-graphrag#the-same-pattern-in-engineering" class="hash-link" aria-label="Direct link to The Same Pattern in Engineering" title="Direct link to The Same Pattern in Engineering">​</a></h2>
<p>Picture an org that went all-in on AI dev agents.</p>
<p>At first, it felt easy. Drop in a PRD, write prompts, and agents created services, wiring, tests, docs. Humans reviewed "for sanity," but reviews got lighter. Hard to argue with green tests and a happy product team.</p>
<p>Six months later, the mood shifts. A service depends on three other services in ways no one can explain. A config flag looks dangerous, but no one knows why it exists. A migration script looks alien. Every change becomes a gamble.</p>
<p>First comes drift: small shortcuts, missing ADRs, half-written docs. Then opacity: you can't explain your own architecture without a week of archaeology. Then fear: people avoid critical areas. Every incident feels like surgery in the dark.</p>
<p>By collapse, you have two options: hire senior engineers to reverse-engineer the system, or rewrite it.</p>
<p><img decoding="async" loading="lazy" alt="The AI-amnesia collapse in five phases: Speed, Drift, Opacity, Fear, and Collapse." src="https://danduh.me/assets/images/first_graph-ee6960b38169262a8142a33dedd79091.webp" width="1364" height="815" class="img_ev3q"></p>
<p>The code "works" most of the time. The AI shipped features. What collapsed was the <strong>shared mental model</strong> of how everything fits together.</p>
<p>Software isn't just code. It's a stack of memory:</p>
<ul>
<li>Product intent and constraints</li>
<li>Architectural decisions and tradeoffs</li>
<li>Invariants that must never break</li>
<li>History of past incidents</li>
<li>Who understands this well enough to touch it</li>
</ul>
<p>AI contributes code. By default, it doesn't preserve the ecosystem.</p>
<p>That's the problem.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="give-ai-and-humans-real-long-term-memory">Give AI (and Humans) Real Long-Term Memory<a href="https://danduh.me/blog/ai-amnesia-graphrag#give-ai-and-humans-real-long-term-memory" class="hash-link" aria-label="Direct link to Give AI (and Humans) Real Long-Term Memory" title="Direct link to Give AI (and Humans) Real Long-Term Memory">​</a></h2>
<p>The fix: capture institutional memory in a structured way.</p>
<p>Build a <strong>Knowledge Graph</strong>. Use <strong>GraphRAG</strong> to give humans and agents access to it.</p>
<p>Here's how it works. Every PRD, ADR, PR, release, incident, and postmortem becomes a node. Relationships become edges. The graph is backed by an archive (the original text), a search index, and a summary layer for token limits.</p>
<p>When an AI agent implements a change, it doesn't start from "here's some code, guess what it means." It starts from "here's the chain of decisions and events that led to this code."</p>
<p>Why GraphRAG? Traditional RAG returns "chunks of text that look similar to my query." That works for local details. Not for questions like:</p>
<ul>
<li>How did we go from the original PRD to last week's incident?</li>
<li>What decisions have we made about this database, and what went wrong?</li>
</ul>
<p>GraphRAG works at the level of entities, relationships, and clusters. It doesn't just retrieve text — it walks and summarizes neighborhoods of the graph. You get the <strong>story</strong> around a piece of code, not just a doc that shares some words with your query.</p>
<p>But this only works if you treat it as a <strong>discipline</strong>, not just storage.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="building-a-memory-system-not-just-a-database">Building a Memory System, Not Just a Database<a href="https://danduh.me/blog/ai-amnesia-graphrag#building-a-memory-system-not-just-a-database" class="hash-link" aria-label="Direct link to Building a Memory System, Not Just a Database" title="Direct link to Building a Memory System, Not Just a Database">​</a></h2>
<p>A real long-term memory system has two sides: what you store, and how you behave.</p>
<p>On the storage side, you keep an immutable archive of everything that matters: PRDs, ADRs, PR discussions, release notes, incident reports, monitoring summaries. You put a hybrid search index on top so you can find things both by exact IDs and by meaning. You extract entities and relationships to build your knowledge graph: which PRDs led to which ADRs, which ADRs governed which components, which PRs implemented them, which releases shipped them, which incidents were linked to them.</p>
<p>You also build a layer of summaries at different resolutions. Each PRD has a short summary. Each component has a description that includes what it does, which invariants it respects, and what it depends on. Each release has a clear description of what changed and what you expected. Each incident tells the story of what broke and what you learned.</p>
<p>That's the cold part of the system: the pipes, the storage, the indexes.</p>
<p>The hot part is how you <strong>change your definition of done</strong>.</p>
<p>"Done" no longer means "tests passed, PR merged." It means "<strong>this change is visible in our memory.</strong>"</p>
<p>A change without an ADR is not done. A risky release without proper notes is not done. An incident that doesn't produce new invariants and updated docs is not done.</p>
<p>The system enforces this. Open a PR that changes a core data model without linking to a PRD or ADR? Blocked. Ship a release touching a critical service without describing expected impact and dashboards? Pipeline complains. Close a major incident without writing what you learned? Process refuses.</p>
<p>Memory is wired into how work flows.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="walking-one-feature-through-the-loop">Walking One Feature Through the Loop<a href="https://danduh.me/blog/ai-amnesia-graphrag#walking-one-feature-through-the-loop" class="hash-link" aria-label="Direct link to Walking One Feature Through the Loop" title="Direct link to Walking One Feature Through the Loop">​</a></h2>
<p>Follow a single feature from idea to incident.</p>
<p>Your PM drops a PRD for "Instant Payouts v2." Clear goals, non-goals, constraints, SLOs. Rollout strategy and success metrics. The PRD becomes a node.</p>
<p>An AI assistant reads it and proposes a plan: which ADRs are relevant, which incidents to re-read, which services will be affected, where new decisions are needed. You adjust the plan, argue with it, correct it, approve it. The plan links back to the PRD and forward to components and risks.</p>
<p>Next comes the ADR. You write — or co-write with the agent — a focused document:</p>
<ul>
<li>How you'll handle balance caching to hit latency targets</li>
<li>What failure modes you considered</li>
<li>Why you rejected a simpler approach</li>
<li>What risks you're accepting</li>
</ul>
<p>Link the ADR back to the PRD and forward to the components that will implement it.</p>
<p>Then code. Agents generate the implementation, but not in a vacuum. For each PR, they see the PRD, the plan, the ADR, and any incidents that touched the same services. They propose changes in that context. Each PR declares what it's doing in human language and links to the artifacts.</p>
<p>Releases roll out with PRs attached, descriptions of what's expected, which dashboards matter. Monitoring episodes get summarized and linked back to suspected releases and components. If something breaks, an incident is declared. Timeline, root cause, fix get captured. New invariants are born: "never do X again," "always verify Y before Z."</p>
<p>Invariants become first-class citizens. Linked to the incidents that birthed them, the ADRs that codified them, the components that must respect them.</p>
<p>The loop looks something like this:</p>
<p><img decoding="async" loading="lazy" alt="The feature memory loop: product requirements → planning → architecture decision → implementation → release → monitoring → incident handling → invariants, feeding back into planning." src="https://danduh.me/assets/images/seconf_graph-466be0ec354ece86dc11bd68314f483f.webp" width="1165" height="816" class="img_ev3q"></p>
<p>Now imagine someone, six months later, wants to change a small function deep inside <code>payouts-core</code>. In a normal "AI wrote most of this" environment, they'd be guessing. In your environment, the agent begins by pulling the decision chain from the knowledge graph: the original PRD, the ADR that justified this odd-looking logic, the incident from two months ago where you accidentally double-paid a thousand users, the invariant that says "never attempt payout if ledger state is older than five minutes."</p>
<p>This doesn't magically guarantee safety. But it radically changes the starting point. The agent is no longer an amnesiac autocomplete engine. It's closer to a junior engineer who has just spent a day reading the history of this subsystem.</p>
<p>The same applies to humans. When a new hire asks, "Why is it built this way?", you don't point them to a ten-thousand-line file and a graveyard of old Slack threads. You give them the story in a structured way: here is the PRD, here are the key decisions, here is when it blew up, here is what we learned.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="what-this-fixes--and-what-it-doesnt">What This Fixes — and What It Doesn't<a href="https://danduh.me/blog/ai-amnesia-graphrag#what-this-fixes--and-what-it-doesnt" class="hash-link" aria-label="Direct link to What This Fixes — and What It Doesn't" title="Direct link to What This Fixes — and What It Doesn't">​</a></h2>
<p>A GraphRAG-backed knowledge graph with real governance eliminates categories of pain.</p>
<p>You stop re-litigating old decisions because the reasoning is recorded. You reduce repeated mistakes because incidents become part of context for future work. You avoid shipping risk-blind changes because agents and humans see which invariants and incidents are attached to the code.</p>
<p>Most importantly, you attack the specific AI failure mode that matters: not "the model hallucinates," but "we built a working system no one understands." This doesn't stop models from being wrong. But it makes it harder for them to be confidently wrong about the history of your own organization.</p>
<p><strong>But there are limits.</strong></p>
<p>Retrieval is not correctness. A model can fetch the right docs and still misapply the lessons. The graph can be wrong if extraction pipelines are noisy or docs are stale. Runtime reality can diverge from docs. A system might claim to be idempotent while production traces tell a different story.</p>
<p>The most dangerous failure: <strong>documentation theater</strong>. If AI writes the code and the "decisions" about the code without human oversight, you risk a beautiful, coherent, fictional history. It will look polished. It will be persuasive. It may be wrong where it matters most.</p>
<p>Governance is not optional. The graph needs human-validated edges. ADRs need actual authors who lived the tradeoffs. Incidents need to be painful enough that people care about capturing lessons. CI needs to enforce that changes are anchored in memory.</p>
<p>Without this, your knowledge graph becomes a museum: pretty, disconnected from life.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-point">The Point<a href="https://danduh.me/blog/ai-amnesia-graphrag#the-point" class="hash-link" aria-label="Direct link to The Point" title="Direct link to The Point">​</a></h2>
<p><img decoding="async" loading="lazy" alt="A knowledge graph linking PRD, ADR, pull request, release, and incident nodes to a summary layer." src="https://danduh.me/assets/images/the_point-a3e4c6a693570a7ad154304526d835a5.webp" width="720" height="480" class="img_ev3q"></p>
<p>The idea is simple.</p>
<p>Give your organization a long-term memory that both humans and AI can see and use. Make that memory structural, not optional. Connect what you meant to build, what you actually built, what broke, and what you swore you'd never do again.</p>
<p>If you pull it off, you don't choose between "AI everywhere, chaos later" and "no AI, we're scared." You get AI that moves fast inside a system that still remembers.</p>
<p>The next time the VP asks, "Who owns <code>billing-orchestrator</code> and why does it behave like this?", you don't get silence and blinking Zoom faces.</p>
<p>You get a story. A graph walk. The decisions, the incidents, the invariants, and a human who can say:</p>
<blockquote>
<p>"Here's what this is, here's how we got here, and here's how we can change it without burning the house down."</p>
</blockquote>
<p>AI generates code. Long-term memory makes sure it's generating a system you can still live in a year from now.</p>]]></content:encoded>
            <category>AI</category>
            <category>Architecture</category>
            <category>GraphRAG</category>
        </item>
    </channel>
</rss>