<?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: AsiaOstrich</title>
    <description>The latest articles on DEV Community by AsiaOstrich (@asiaostrich).</description>
    <link>https://dev.to/asiaostrich</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%2F3975421%2F277bef48-d7d0-4815-b9f6-7512a713c0da.png</url>
      <title>DEV Community: AsiaOstrich</title>
      <link>https://dev.to/asiaostrich</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/asiaostrich"/>
    <language>en</language>
    <item>
      <title>The Biggest Flaw in My AI Evaluation Wasn't the Models. It Was My Scorecard.</title>
      <dc:creator>AsiaOstrich</dc:creator>
      <pubDate>Sat, 25 Jul 2026 11:05:32 +0000</pubDate>
      <link>https://dev.to/asiaostrich/the-biggest-flaw-in-my-ai-evaluation-wasnt-the-models-it-was-my-scorecard-556d</link>
      <guid>https://dev.to/asiaostrich/the-biggest-flaw-in-my-ai-evaluation-wasnt-the-models-it-was-my-scorecard-556d</guid>
      <description>&lt;p&gt;I recently ran a small evaluation to compare three AI coding assistants. The task sounded straightforward: give each model the same engineering artifact, ask it to review the work, then score the results against a checklist I had prepared beforehand.&lt;/p&gt;

&lt;p&gt;I expected to learn which model was the better reviewer. Instead, I learned something much more uncomfortable: my evaluation couldn't reward the behavior I actually wanted.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scorecard had a blind spot
&lt;/h2&gt;

&lt;p&gt;The checklist covered every category of issue I expected a competent reviewer to raise. Each finding earned points if it matched the checklist. That looked objective — until I realized what happened to findings that weren't on the list.&lt;/p&gt;

&lt;p&gt;They automatically became false positives.&lt;/p&gt;

&lt;p&gt;In other words, my evaluation couldn't possibly reward a model for discovering something I had missed. It only rewarded agreement with my own thinking.&lt;/p&gt;

&lt;p&gt;I wasn't measuring "who reviews better." I was measuring "who thinks more like me." Those are not the same question.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix wasn't a better checklist
&lt;/h2&gt;

&lt;p&gt;At first I thought I simply needed a more complete checklist. That wasn't enough. No matter how much I expanded it, there would always be things I hadn't anticipated.&lt;/p&gt;

&lt;p&gt;So I separated the results into two categories: &lt;strong&gt;expected findings&lt;/strong&gt;, and &lt;strong&gt;new findings outside the checklist&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The important part wasn't creating the second column. It was refusing to judge it myself. If the entire purpose of that column is to capture things I failed to notice, I'm the least qualified person to decide whether those findings are valuable. So someone else reviewed those discoveries independently.&lt;/p&gt;

&lt;p&gt;That changed the evaluation completely.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result surprised me
&lt;/h2&gt;

&lt;p&gt;The new category produced two genuine issues that weren't on my original checklist. Both were things I had overlooked.&lt;/p&gt;

&lt;p&gt;Even more interesting, each one came from a different model — and neither model surfaced the other's finding. If I had picked a single "winner," one of those two issues would never have shown up.&lt;/p&gt;

&lt;p&gt;That made me rethink the question itself. Instead of asking &lt;em&gt;"Which reviewer is the best?"&lt;/em&gt; I started asking &lt;em&gt;"Which reviewers complement each other?"&lt;/em&gt; Those are very different evaluation goals.&lt;/p&gt;

&lt;h2&gt;
  
  
  Role mattered more than model
&lt;/h2&gt;

&lt;p&gt;Another observation was just as interesting. When the task was "follow explicit instructions and revise the work," the models performed similarly. When the task became "find something nobody explicitly asked you to find," the differences became much larger.&lt;/p&gt;

&lt;p&gt;The ranking wasn't universal. It depended on the role.&lt;/p&gt;

&lt;p&gt;That sounds obvious in hindsight, but many comparisons quietly assume a model has a single global ranking across every engineering task. My experiment suggested otherwise. Choosing an editor and choosing a reviewer may be two entirely different optimization problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed for me
&lt;/h2&gt;

&lt;p&gt;I still use checklists. They're valuable because they make evaluations repeatable. But now I treat them as only half of the measurement. The other half is explicitly looking for discoveries that the checklist couldn't have predicted.&lt;/p&gt;

&lt;p&gt;Otherwise, an evaluation becomes surprisingly good at rewarding agreement — and surprisingly bad at finding the thing you actually wanted someone to notice.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: I used AI assistance to improve the English wording of this article. The engineering experience, analysis, and conclusions are my own.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>ai</category>
      <category>codereview</category>
      <category>testing</category>
    </item>
    <item>
      <title>A token-saving repo went viral. We read the source, not the README.</title>
      <dc:creator>AsiaOstrich</dc:creator>
      <pubDate>Wed, 08 Jul 2026 12:00:11 +0000</pubDate>
      <link>https://dev.to/asiaostrich/a-token-saving-repo-went-viral-we-read-the-source-not-the-readme-9bn</link>
      <guid>https://dev.to/asiaostrich/a-token-saving-repo-went-viral-we-read-the-source-not-the-readme-9bn</guid>
      <description>&lt;p&gt;Our daily scout surfaces AI-tooling repos every morning. Most of them you should &lt;em&gt;not&lt;/em&gt; adopt — and the engineering is in deciding &lt;em&gt;which parts&lt;/em&gt;, if any, are actually load-bearing for your problem. Here's one worked example.&lt;/p&gt;

&lt;p&gt;A context-compression project — &lt;strong&gt;Headroom&lt;/strong&gt; (&lt;code&gt;headroomlabs-ai/headroom&lt;/code&gt;, Apache-2.0) — made the rounds with a great pitch: cut 60–95% of your LLM context tokens with "zero degradation." For an agent that streams huge tool logs into a model, that's the dream. The reflex it triggers in most teams: &lt;em&gt;vendor the whole thing.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We did the slower thing first. We opened the source.&lt;/p&gt;

&lt;h2&gt;
  
  
  The README names and the real names disagree
&lt;/h2&gt;

&lt;p&gt;Headroom's README sells three components — CodeCompressor, SmartCrusher, CacheAligner. Open &lt;code&gt;src/&lt;/code&gt; and the actual classes are &lt;code&gt;CodeStructureHandler&lt;/code&gt;, &lt;code&gt;JSONStructureHandler&lt;/code&gt;, and &lt;code&gt;AnthropicCacheOptimizer&lt;/code&gt; + &lt;code&gt;PrefixCacheTracker&lt;/code&gt;. Not a scandal — just a reminder that the README is marketing copy and the source is the spec. The real design is a clean three-stage pipeline: detect the content type (Magika), run a type-specific handler to extract a structural skeleton, and only then lossy-compress whatever's left.&lt;/p&gt;

&lt;p&gt;The parts worth understanding:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Code&lt;/strong&gt; (&lt;code&gt;handlers/code_handler.py&lt;/code&gt;): tree-sitter parses the file (regex fallback) and keeps imports, signatures, and class declarations while dropping function bodies, comments, and whitespace. "Keep the API surface, drop the implementation." Works for Python/JS/TS/Go/Rust/Java/Perl.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON&lt;/strong&gt; (&lt;code&gt;handlers/json_handler.py&lt;/code&gt;): a per-token heuristic — keep keys, syntax, booleans, short values, and high-entropy strings (entropy ≥ 0.85); compress long strings; keep the first N array items in full. No schema, no dedup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache alignment&lt;/strong&gt; (&lt;code&gt;prefix_tracker.py&lt;/code&gt;): one genuinely sharp invariant — &lt;em&gt;freeze the already-cached prefix, only compress new content&lt;/em&gt;, so your compression never rewrites the prefix and silently invalidates the provider's KV cache.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The benchmark, read adversarially
&lt;/h2&gt;

&lt;p&gt;This is where reading beats retweeting. The headline is "60–95% savings, zero degradation," measured with the standard lm-eval harness on a real model. Credit where due: the repo even ships its own worst-case and adversarial benchmarks and lists its failure modes — an honest signal most projects skip.&lt;/p&gt;

&lt;p&gt;But line up the claim with what was actually measured:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The zero-degradation numbers come from &lt;strong&gt;GSM8K / TruthfulQA / SQuAD&lt;/strong&gt; — short-answer tasks with almost no context to compress. "Zero degradation" there is &lt;em&gt;trivially&lt;/em&gt; true; it can't support the 60–95% claim.&lt;/li&gt;
&lt;li&gt;The runs that &lt;em&gt;do&lt;/em&gt; compress heavily — agent traces — are &lt;strong&gt;n=5&lt;/strong&gt;, scored by loose keyword matching.&lt;/li&gt;
&lt;li&gt;It's all self-reported, with no third-party reproduction. One external review of prompt-heavy chat measured &lt;strong&gt;20–40% savings&lt;/strong&gt;, not 60–95%.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of that makes Headroom bad. It makes the headline number a &lt;em&gt;claim&lt;/em&gt;, not a result — and the difference matters when you're deciding what to build on.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we borrowed — and what we left on the shelf
&lt;/h2&gt;

&lt;p&gt;We took three &lt;strong&gt;ideas&lt;/strong&gt;, each re-implemented to fit our own stack, not the package:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;tree-sitter "keep signatures, drop bodies"&lt;/strong&gt; for code blobs — a real semantic upgrade over blunt truncation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The JSON keep-keys / high-entropy heuristic&lt;/strong&gt; — cheap, deterministic, no model required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache-aware compression&lt;/strong&gt; — the "will this rewrite a cached prefix?" check, which is the highest-ROI idea in the whole repo and the one nobody talks about.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We explicitly &lt;strong&gt;declined&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The reversible-retrieve loop (CCR)&lt;/strong&gt; — it carries an extra store plus a retrieve round-trip to &lt;em&gt;recover&lt;/em&gt; what compression dropped, costs hundreds of MB of RAM, and is already known to break under streaming (issue #1450). If your compression is allowed to be lossy, you don't need it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The ML text model (Kompress)&lt;/strong&gt; — small, English-biased, and when it's unavailable the whole text path silently falls back to first-2/3 + last-1/3 truncation. A dependency that decays into the thing it replaced.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The proxy / MCP server / cross-agent "learn loop"&lt;/strong&gt; — orthogonal feature creep, not a compression upgrade.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The actual lesson
&lt;/h2&gt;

&lt;p&gt;A viral repo is an input to a decision, not the decision. Read the source to find the two or three load-bearing ideas, check the benchmark against what it actually measured, and write down — on the record — what you &lt;em&gt;didn't&lt;/em&gt; take and why. That discipline (read the code, not the README; borrow only what nets out positive) is exactly what we encode as a standard.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;AI disclosure: I wrote this with AI assistance for English phrasing and structure. The source reading, the benchmark analysis, and the adopt/decline decisions are my own — the AI did not supply the technical judgement.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>opensource</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Picking a code-graph memory for your AI agent? Star count is the wrong axis.</title>
      <dc:creator>AsiaOstrich</dc:creator>
      <pubDate>Mon, 06 Jul 2026 12:00:11 +0000</pubDate>
      <link>https://dev.to/asiaostrich/picking-a-code-graph-memory-for-your-ai-agent-star-count-is-the-wrong-axis-2kie</link>
      <guid>https://dev.to/asiaostrich/picking-a-code-graph-memory-for-your-ai-agent-star-count-is-the-wrong-axis-2kie</guid>
      <description>&lt;p&gt;"Code memory for coding agents" is a hot lane right now. A 49k-star code-graph tool shows up, FalkorDB is pushing GraphRAG, and the reflex is the same one we apply to everything popular: &lt;em&gt;lots of stars — let's wire it in.&lt;/em&gt; We ran one through our radar instead. Here's the part worth keeping: not the verdict, but the axis we used to reach it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lane has two very different shapes
&lt;/h2&gt;

&lt;p&gt;Most "memory for your agent" is a vector database: chunk the repo, embed it, retrieve by similarity. That is genuinely good at one thing — &lt;em&gt;finding code that looks like other code&lt;/em&gt;. Paste a function, get back functions shaped like it.&lt;/p&gt;

&lt;p&gt;But the question I actually ask every day isn't "what looks similar to this." It's:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"If I change this function, what breaks?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Similarity can't answer that, and not by accident. &lt;strong&gt;The callers of a function look nothing like the function.&lt;/strong&gt; A logging wrapper, a retry decorator, a route handler three layers up — none of them are textually similar to the body you're editing. They're connected by &lt;em&gt;structure&lt;/em&gt;, not by surface. Answering "what breaks" is a graph traversal — walk the call edges — not a nearest-neighbour lookup in embedding space.&lt;/p&gt;

&lt;p&gt;That's the real divide in this lane: &lt;strong&gt;similarity retrieval vs structural (graph) retrieval.&lt;/strong&gt; Almost everything in the hype cycle is the former. The thing you reach for during a risky refactor is the latter.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we did with a 49k-star tool
&lt;/h2&gt;

&lt;p&gt;CodeGraph (MIT, ~49k stars, 20+ languages, auto-syncing local index, MCP server for Cursor/Claude Code/Codex) is a serious piece of work on the &lt;em&gt;structural&lt;/em&gt; side. We evaluated it properly — read the repo, mapped its capabilities against ours.&lt;/p&gt;

&lt;p&gt;And we deliberately &lt;strong&gt;did not adopt it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not out of not-invented-here. Our own engine, EngramGraph, already covers the core: a tree-sitter code graph where &lt;code&gt;callers &amp;lt;fn&amp;gt;&lt;/code&gt; &lt;em&gt;is&lt;/em&gt; a graph walk, plus a deterministic, zero-LLM index (no embeddings to drift, cheap to rebuild, same answer every run). The decision to build rather than wire in an external daemon was already made; adopting CodeGraph wholesale would have added a dependency and an architecture conflict, not a capability.&lt;/p&gt;

&lt;p&gt;So we filed it as &lt;strong&gt;Assess&lt;/strong&gt;, and wrote down the three things it's genuinely useful &lt;em&gt;for&lt;/em&gt; — without adopting it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A language-coverage blueprint.&lt;/strong&gt; CodeGraph parses 20+ languages and recognises 17+ framework routing patterns. We don't import its code; we use its parser list as a reference map for where to extend ours next.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A benchmark method.&lt;/strong&gt; Its 7-codebase, four-axis measurement (cost / tool-calls / tokens / latency) is a reusable &lt;em&gt;yardstick template&lt;/em&gt; for proving a code graph actually reduces agent exploration — not a set of numbers to copy. Their absolute figures are theirs; the method travels, the numbers don't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An interop signal.&lt;/strong&gt; It exposes its graph over MCP to several agents. That's market evidence that "cross-agent code-graph over MCP" is wanted — worth watching, not worth coupling to.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The point isn't the verdict
&lt;/h2&gt;

&lt;p&gt;A "no" with reasons is more useful six months later than a "yes" you can't reconstruct. The lesson that outlives this particular tool:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool selection in a hyped lane is not a star-count comparison.&lt;/strong&gt; It's one question — &lt;em&gt;does this solve the thing I actually ask every day?&lt;/em&gt; For code memory, that question splits cleanly along similarity-vs-structure. Once you know which side of that line your real problem lives on, half the shiny options on the radar quietly stop being relevant.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;AI disclosure: I wrote this with AI assistance for English phrasing and structure. The repo reading and the adopt/decline decision are my own — the AI did not supply the technical judgement.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>database</category>
      <category>rag</category>
    </item>
    <item>
      <title>Most AI-generated tests are written once and never questioned. That's the bug.</title>
      <dc:creator>AsiaOstrich</dc:creator>
      <pubDate>Thu, 02 Jul 2026 12:00:25 +0000</pubDate>
      <link>https://dev.to/asiaostrich/most-ai-generated-tests-are-written-once-and-never-questioned-thats-the-bug-5e18</link>
      <guid>https://dev.to/asiaostrich/most-ai-generated-tests-are-written-once-and-never-questioned-thats-the-bug-5e18</guid>
      <description>&lt;p&gt;Our daily scout surfaces AI-tooling papers every morning. Most of them don't change how we build. This one made us look hard at a habit we'd stopped noticing — so here's the worked example.&lt;/p&gt;

&lt;p&gt;In January 2026, &lt;strong&gt;"The Rise of Agentic Testing"&lt;/strong&gt; (&lt;a href="https://arxiv.org/abs/2601.02454" rel="noopener noreferrer"&gt;arXiv:2601.02454&lt;/a&gt;) made the case that the way we generate tests with LLMs is quietly broken. Not the models — the &lt;em&gt;shape&lt;/em&gt; of the loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  The default loop is single-shot
&lt;/h2&gt;

&lt;p&gt;Ask an LLM for tests and you usually get them in one pass: generate once, done. The problem is that a one-shot test is written &lt;em&gt;blind&lt;/em&gt; — the model never sees the test actually run. So you get the three failure modes everyone has hit: tests that don't compile, tests that assert nothing meaningful, tests that duplicate each other. There's no execution-aware feedback closing the loop, so a test that &lt;em&gt;passes for the wrong reason&lt;/em&gt; sails straight through.&lt;/p&gt;

&lt;p&gt;The paper's proposal: make test generation a closed loop — generate, run in a sandbox, analyze the failure, then &lt;strong&gt;refine the test itself&lt;/strong&gt;, and iterate until it converges. Standard agentic-loop thinking, pointed at the test instead of the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  code-centric vs test-centric — the part worth internalizing
&lt;/h2&gt;

&lt;p&gt;Here's the distinction that's actually load-bearing.&lt;/p&gt;

&lt;p&gt;When a test fails, most pipelines (ours included, historically) are &lt;strong&gt;code-centric&lt;/strong&gt;: the failure is a signal to &lt;em&gt;change the code until the test goes green&lt;/em&gt;. The test is treated as ground truth.&lt;/p&gt;

&lt;p&gt;But the test isn't always right. Sometimes the failing test is the one telling the truth and the "fix" is to weaken an assertion until it stops complaining. A &lt;strong&gt;test-centric&lt;/strong&gt; loop asks a different question first: &lt;em&gt;is this failure because the code is wrong, or because the test is imprecise / asserting the wrong thing?&lt;/em&gt; If it's the test, you fix the &lt;strong&gt;test&lt;/strong&gt; — make it a sharper check — instead of bending the code to satisfy a bad check.&lt;/p&gt;

&lt;p&gt;Why this matters beyond semantics: a test that passes for the wrong reason is worse than no test. It's a &lt;strong&gt;false green&lt;/strong&gt; — it merges, it sits in CI looking like coverage, and it gives everyone permission to stop paying attention to exactly the thing it was supposed to guard. That's not coverage. That's debt wearing a coverage badge.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest caveat (this is the discipline, not a footnote)
&lt;/h2&gt;

&lt;p&gt;We could only get the paper's &lt;strong&gt;abstract&lt;/strong&gt;. No full text on arXiv, no HTML, and &lt;strong&gt;no public repo&lt;/strong&gt; — so there was no implementation to read and no methodology to verify. The paper also headlines numbers like "−60% invalid tests / +30% coverage."&lt;/p&gt;

&lt;p&gt;We are explicitly &lt;strong&gt;not&lt;/strong&gt; adopting those numbers. They're a single-source claim, measured on one microservice app, unreproduced, and from a domain that isn't ours. Porting a benchmark figure you can't reproduce into your own targets is how you manufacture a result and call it evidence. Every threshold we'd ever set has to be a &lt;em&gt;relative&lt;/em&gt; comparison against our own baseline — not "the paper got X, so we should too."&lt;/p&gt;

&lt;p&gt;So what &lt;em&gt;did&lt;/em&gt; survive the filter? One idea: &lt;strong&gt;when a test fails, first decide whether the test or the code is wrong&lt;/strong&gt; — and if it's the test, refine the test. That's it. Not the multi-agent topology, not the numbers. The one load-bearing idea.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this lands for us
&lt;/h2&gt;

&lt;p&gt;A test that's allowed to refine itself into a stronger check is just the natural extension of an anti-fake test gate — the kind of gate that flags a test which asserts nothing and quietly passes. Detection is step one; turning a weak test into a real check is step two. We treat a passing test as a &lt;em&gt;check that has to keep being true&lt;/em&gt;, not a one-time green tick.&lt;/p&gt;

&lt;p&gt;Half of staying current is reading the papers. The other half is the discipline to take only the one idea that's actually load-bearing for &lt;em&gt;your&lt;/em&gt; problem — and to refuse the numbers you can't reproduce.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;AI disclosure: I wrote this with AI assistance for English phrasing and structure. The paper reading and the judgement about which idea was load-bearing are my own — the AI did not supply the technical judgement.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>llm</category>
      <category>testing</category>
    </item>
    <item>
      <title>Our graph database was abandoned upstream — here's the 6-line migration (EngramGraph 0.3.0)</title>
      <dc:creator>AsiaOstrich</dc:creator>
      <pubDate>Wed, 24 Jun 2026 12:00:26 +0000</pubDate>
      <link>https://dev.to/asiaostrich/our-graph-database-was-abandoned-upstream-heres-the-6-line-migration-engramgraph-030-5h1p</link>
      <guid>https://dev.to/asiaostrich/our-graph-database-was-abandoned-upstream-heres-the-6-line-migration-engramgraph-030-5h1p</guid>
      <description>&lt;p&gt;In October 2025, Kùzu Inc. archived KuzuDB — the embedded graph database — with a one-line note: "Kuzu is working on something new." The npm package was deprecated, and its transitive dependencies (&lt;code&gt;tar@6.2.1&lt;/code&gt;, &lt;code&gt;npmlog&lt;/code&gt;, &lt;code&gt;gauge&lt;/code&gt;) carried 5 high-severity vulnerabilities with no fix coming.&lt;/p&gt;

&lt;p&gt;EngramGraph runs on an embedded graph database. That database was Kuzu.&lt;/p&gt;

&lt;p&gt;EngramGraph 0.3.0 is the migration release — and the migration turned out to be far cheaper than expected. Here's why, and what we learned.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: ryugraph
&lt;/h2&gt;

&lt;p&gt;The community responded to KuzuDB's abandonment with several forks. We evaluated them and picked &lt;a href="https://github.com/predictable-labs/ryugraph" rel="noopener noreferrer"&gt;ryugraph&lt;/a&gt; (Predictable Labs):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MIT licensed&lt;/strong&gt;, actively maintained, published on npm&lt;/li&gt;
&lt;li&gt;Designed as a &lt;strong&gt;drop-in Kuzu replacement&lt;/strong&gt; — and in our experience, it actually is&lt;/li&gt;
&lt;li&gt;Same storage format lineage: existing &lt;code&gt;.engram/graph.db&lt;/code&gt; files keep working&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The core API — &lt;code&gt;prepare()&lt;/code&gt;, &lt;code&gt;execute()&lt;/code&gt;, &lt;code&gt;query()&lt;/code&gt;, &lt;code&gt;getAll()&lt;/code&gt; — is signature-identical to kuzu. Our entire test suite (69 tests) passed on the first run after the swap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the migration was 6 lines
&lt;/h2&gt;

&lt;p&gt;One architectural decision from day one paid for itself here: &lt;strong&gt;every raw database call goes through a single thin wrapper&lt;/strong&gt; (&lt;code&gt;GraphConnection&lt;/code&gt;, ~80 lines). The other 20+ call sites in the codebase only ever see the wrapper.&lt;/p&gt;

&lt;p&gt;So the migration was:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;package.json&lt;/code&gt;: &lt;code&gt;kuzu&lt;/code&gt; → &lt;code&gt;ryugraph&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;connection.ts&lt;/code&gt;: one import line&lt;/li&gt;
&lt;li&gt;Two type-only imports (&lt;code&gt;KuzuValue&lt;/code&gt; → &lt;code&gt;RyuValue&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. If your project wraps its native dependencies behind one interface, an upstream abandonment becomes an afternoon, not a quarter.&lt;/p&gt;

&lt;h2&gt;
  
  
  The leftover CVEs (and the override trick)
&lt;/h2&gt;

&lt;p&gt;Swapping kuzu killed the deprecated-toolchain CVEs, but ryugraph itself pins &lt;code&gt;cmake-js@^7.3.0&lt;/code&gt;, whose &lt;code&gt;tar@6.2.1&lt;/code&gt; carries known path-traversal CVEs. The fix exists upstream (&lt;code&gt;cmake-js@8&lt;/code&gt; uses a patched tar) — ryugraph just hasn't bumped yet.&lt;/p&gt;

&lt;p&gt;npm &lt;code&gt;overrides&lt;/code&gt; to the rescue:&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="nl"&gt;"overrides"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"cmake-js"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^8.0.0"&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;&lt;code&gt;npm audit&lt;/code&gt;: &lt;strong&gt;5 high → 0&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;One caveat worth knowing: &lt;strong&gt;npm overrides don't propagate to downstream consumers&lt;/strong&gt;. If you depend on &lt;code&gt;engramgraph&lt;/code&gt; (or anything that depends on ryugraph), add the same override to your own &lt;code&gt;package.json&lt;/code&gt; until ryugraph bumps cmake-js upstream.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;AI disclosure: I wrote this with AI assistance for English phrasing and structure. The migration, the CVE work, and the wrapper decision that made it six lines are my own — the AI did not supply the technical judgement.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>database</category>
      <category>javascript</category>
      <category>opensource</category>
      <category>security</category>
    </item>
    <item>
      <title>Seven kinds of spec ↔ test ↔ code ↔ doc drift — and why naming them makes them checkable</title>
      <dc:creator>AsiaOstrich</dc:creator>
      <pubDate>Wed, 24 Jun 2026 01:10:29 +0000</pubDate>
      <link>https://dev.to/asiaostrich/make-spec-test-code-doc-drift-visible-sdd-analyze-in-uds-5170-2kli</link>
      <guid>https://dev.to/asiaostrich/make-spec-test-code-doc-drift-visible-sdd-analyze-in-uds-5170-2kli</guid>
      <description>&lt;p&gt;Spec-driven development has a quiet failure mode: your specs, tests, &lt;code&gt;.feature&lt;/code&gt; files, source, and user docs all start aligned — and then drift apart. A test loses its spec. An acceptance criterion never gets a test. A spec is marked done but was never implemented. Nobody notices until it surfaces in review or production.&lt;/p&gt;

&lt;p&gt;The problem with "are the artifacts still consistent?" is that it isn't one question. It's several, and each has a different mechanical answer. Naming them separately is what makes the check automatable instead of a review-time judgement call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seven kinds of cross-artifact drift
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Orphan test&lt;/strong&gt; — a test with no spec behind it. Either the spec was deleted, or the test is guarding something nobody agreed to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Uncovered AC&lt;/strong&gt; — an acceptance criterion with no test. The requirement exists on paper and nowhere else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;not_implemented&lt;/strong&gt; — a spec marked done but absent in code. The dangerous one, because the status field asserts the opposite of the truth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-spec conflict&lt;/strong&gt; — two specs that contradict each other. Whichever one you read last wins.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orphan &lt;code&gt;.feature&lt;/code&gt;&lt;/strong&gt; — a feature file with no owning spec. BDD scenarios that outlived their requirement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AC-without-scenario&lt;/strong&gt; — an acceptance criterion that no BDD scenario exercises. Possibly covered by unit tests, but never described as behaviour.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;user-guide ↔ E2E drift&lt;/strong&gt; — docs and end-to-end tests out of sync. Two artifacts that both claim to describe what the user sees, disagreeing.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why the enumeration is the point
&lt;/h2&gt;

&lt;p&gt;Each of the seven is a link that should resolve and doesn't: spec→test, AC→test, spec→code, spec→spec, feature→spec, AC→scenario, doc→E2E. Once you state them that way, checking is a graph walk and a report of the edges that dangle — not a reading exercise.&lt;/p&gt;

&lt;p&gt;That difference matters because a consistency check performed by reading only happens when someone remembers to read. A check expressed as dangling edges can run on every commit, and the version that runs on every commit is the only one that stays true.&lt;/p&gt;

&lt;p&gt;The list is not exhaustive. It's the set that is cheap enough to check mechanically, which is exactly what makes it worth automating rather than intending.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;AI disclosure: I wrote this with AI assistance for English phrasing and structure. The drift taxonomy and the decision about which links are worth checking are my own — the AI did not supply the technical judgement.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>news</category>
      <category>productivity</category>
      <category>testing</category>
      <category>tooling</category>
    </item>
    <item>
      <title>A hyped memory paper dropped. We didn't adopt it — and that was the point.</title>
      <dc:creator>AsiaOstrich</dc:creator>
      <pubDate>Mon, 15 Jun 2026 12:00:25 +0000</pubDate>
      <link>https://dev.to/asiaostrich/a-hyped-memory-paper-dropped-we-didnt-adopt-it-and-that-was-the-point-4de2</link>
      <guid>https://dev.to/asiaostrich/a-hyped-memory-paper-dropped-we-didnt-adopt-it-and-that-was-the-point-4de2</guid>
      <description>&lt;p&gt;Our daily scout surfaces AI-tooling papers every morning. Most weeks the disciplined answer to a shiny new architecture is "no" — and &lt;em&gt;why&lt;/em&gt; you say no is where the engineering judgment lives. Here's one worked example.&lt;/p&gt;

&lt;p&gt;In May 2026, &lt;strong&gt;SAGE: A Self-Evolving Agentic Graph-Memory Engine&lt;/strong&gt; (&lt;a href="https://arxiv.org/abs/2605.12061" rel="noopener noreferrer"&gt;arXiv:2605.12061&lt;/a&gt;) made the rounds. The pitch is genuinely good: turn graph memory from a passive retrieval middleware into a &lt;em&gt;dynamic long-term substrate&lt;/em&gt;, via two coupled components — a &lt;strong&gt;Memory Writer&lt;/strong&gt; that incrementally builds the graph from interaction history, and a &lt;strong&gt;Memory Reader&lt;/strong&gt; that retrieves &lt;em&gt;and feeds back&lt;/em&gt; to the writer. That reader→writer feedback loop is the self-evolution engine. The paper reports strong multi-hop QA results, best after two self-evolution rounds.&lt;/p&gt;

&lt;p&gt;The reflex that paper triggers in most teams: &lt;em&gt;new SOTA memory architecture — let's rebuild on it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We did the opposite. We opened our own design doc first.&lt;/p&gt;

&lt;h2&gt;
  
  
  We already had it
&lt;/h2&gt;

&lt;p&gt;EngramGraph (our open-source code + knowledge-graph memory engine) already specifies a &lt;strong&gt;SAGE Evolution Loop&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;Writer&lt;/strong&gt; that updates node confidence in the graph from &lt;em&gt;feedback events&lt;/em&gt; — test pass/fail, human corrections, spec-status changes;&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;Reader&lt;/strong&gt; that returns high-confidence nodes first on query, and feeds that signal back to the Writer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Line that up against the paper:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;SAGE paper&lt;/th&gt;
&lt;th&gt;EngramGraph (existing design)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Memory Writer, incremental build&lt;/td&gt;
&lt;td&gt;Writer: feedback event → update node confidence / edges&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory Reader, retrieve + feed back&lt;/td&gt;
&lt;td&gt;Reader: high-confidence-first → feeds back to Writer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Downstream feedback drives evolution&lt;/td&gt;
&lt;td&gt;Signals: test pass/fail, human fix, spec-status change&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Architecturally isomorphic.&lt;/strong&gt; Adopting the paper's architecture wholesale would have added roughly nothing — except a rewrite, a migration, and the risk that comes with both.&lt;/p&gt;

&lt;h2&gt;
  
  
  So we borrowed the rigor, not the architecture
&lt;/h2&gt;

&lt;p&gt;A paper that &lt;em&gt;matches&lt;/em&gt; your design is still valuable — just not as a blueprint. It's valuable as &lt;strong&gt;backing and method&lt;/strong&gt;. We extracted exactly the three things our design was actually missing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Formalization&lt;/strong&gt; — academic grounding for &lt;em&gt;why&lt;/em&gt; a reader-writer feedback loop beats static-graph retrieval. Useful when you have to defend a design, not just ship it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A validation method&lt;/strong&gt; — the paper's benchmark thinking inspired a code-graph &lt;em&gt;memory-quality yardstick&lt;/em&gt;, which moves our loop from "designed" to "measurably verified." This is the real prize: a way to prove the thing works, not just assert it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Iteration-rounds design&lt;/strong&gt; — the paper's "best after two self-evolution rounds" gave us a convergence-rounds definition our loop simply didn't have.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The honest caveat (this is the part that matters)
&lt;/h2&gt;

&lt;p&gt;Here's the line we drew, in writing, before borrowing anything:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The paper's benchmarks are multi-hop QA, Natural Questions, LongMemEval, HaluMem — &lt;strong&gt;none of which is a code-graph scenario&lt;/strong&gt;. The absolute numbers do not transfer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So we explicitly &lt;strong&gt;refuse to port the paper's numbers&lt;/strong&gt; into our domain. Every threshold in our borrow record is a &lt;em&gt;relative&lt;/em&gt; comparison — confidence-weighted reader vs. our own static-MERGE baseline — not "the paper got 91.6, so we should too." Cargo-culting a benchmark from dialogue memory into a code graph is exactly how you manufacture fiction and call it a result.&lt;/p&gt;

&lt;p&gt;We also wrote down what we deliberately &lt;strong&gt;did not&lt;/strong&gt; take: the paper's agent-dialogue-memory scenario doesn't fit a code graph, so its scenario-specific mechanics stay on the shelf.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why log a "no" at all
&lt;/h2&gt;

&lt;p&gt;Because a tech radar isn't a list of things you adopted. It's a record of &lt;em&gt;judgment&lt;/em&gt;. We filed SAGE as &lt;strong&gt;Trial&lt;/strong&gt;, scoped to relative validation, with the non-adoptions named. Six months from now, that record tells us — and anyone reading — not just &lt;em&gt;what&lt;/em&gt; we believed, but &lt;em&gt;how carefully&lt;/em&gt; we believed it.&lt;/p&gt;

&lt;p&gt;Half of staying current is reading everything. The other half is the discipline to take only the part that's actually load-bearing for &lt;em&gt;your&lt;/em&gt; problem.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;AI disclosure: I wrote this with AI assistance for English phrasing and structure. The paper reading and the decision not to adopt are my own — the AI did not supply the technical judgement.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>machinelearning</category>
      <category>rag</category>
    </item>
  </channel>
</rss>
