<?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: Julien Chable</title>
    <description>The latest articles on DEV Community by Julien Chable (@julien_chable).</description>
    <link>https://dev.to/julien_chable</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%2F4049224%2F5f7150a2-62bf-46fa-b034-04ce97614bdb.jpeg</url>
      <title>DEV Community: Julien Chable</title>
      <link>https://dev.to/julien_chable</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/julien_chable"/>
    <language>en</language>
    <item>
      <title>I ported a knowledge-format (OKF) library to zero-dependency .NET — here's what I learned</title>
      <dc:creator>Julien Chable</dc:creator>
      <pubDate>Mon, 27 Jul 2026 10:09:05 +0000</pubDate>
      <link>https://dev.to/julien_chable/i-ported-a-knowledge-format-okf-library-to-zero-dependency-net-heres-what-i-learned-1mb7</link>
      <guid>https://dev.to/julien_chable/i-ported-a-knowledge-format-okf-library-to-zero-dependency-net-heres-what-i-learned-1mb7</guid>
      <description>&lt;p&gt;If you can &lt;code&gt;cat&lt;/code&gt; a file, you can read the knowledge base. If you can &lt;code&gt;git clone&lt;/code&gt; a repo, you can ship it. No vector database to stand up, no proprietary export format, no vendor lock-in — just a directory of markdown files with YAML frontmatter that a human can open in any editor and an agent can read with &lt;code&gt;ReadFile&lt;/code&gt;. That's the whole pitch, and it's the reason I spent the last few weeks porting a library to C# to get it onto .NET.&lt;/p&gt;

&lt;p&gt;The format behind that pitch is Google's &lt;a href="https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md" rel="noopener noreferrer"&gt;Open Knowledge Format (OKF) v0.2&lt;/a&gt;, and the library is &lt;a href="https://github.com/jchable/okf4net" rel="noopener noreferrer"&gt;&lt;strong&gt;OKF4net&lt;/strong&gt;&lt;/a&gt; (&lt;a href="https://jchable.github.io/okf4net/" rel="noopener noreferrer"&gt;docs &amp;amp; project site&lt;/a&gt;) — a zero-dependency .NET&lt;br&gt;
 (C#, net10.0) implementation, plus an optional layer for wiring OKF bundles straight into agents built on the Microsoft Agent Framework. This post is the launch story: what OKF actually is, why I ported it instead of writing a wrapper, and what "zero dependency" really costs and buys you.&lt;/p&gt;
&lt;h2&gt;
  
  
  What OKF is
&lt;/h2&gt;

&lt;p&gt;OKF defines a &lt;strong&gt;bundle&lt;/strong&gt;: a directory tree of UTF-8 markdown files, where each file is a &lt;strong&gt;concept&lt;/strong&gt; — a YAML frontmatter block followed by a markdown body. Concepts cross-link each other with ordinary markdown links, &lt;code&gt;index.md&lt;/code&gt; files give you progressive-disclosure directory listings, and &lt;code&gt;log.md&lt;/code&gt; files record date-grouped change history. The only hard conformance requirement is a non-empty &lt;code&gt;type&lt;/code&gt; field on every concept; everything else — unknown types, unknown keys, broken links — has to be tolerated by a conformant consumer. It's deliberately boring as a format, which is the point: the format is context here, not the pitch. The pitch is what you get to do with plain files — diff them, review them in a PR, grep them, back them up with nothing but &lt;code&gt;git&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  The port story
&lt;/h2&gt;

&lt;p&gt;This repository used to ship a Rust implementation of OKF. I removed it at commit &lt;code&gt;d20343c&lt;/code&gt; — but only after proving, file by file and command by command, that the C# port produced byte-identical output. &lt;code&gt;tests/fixtures/golden/&lt;/code&gt; holds five golden captures taken directly from the Rust binary's stdout — &lt;code&gt;validate&lt;/code&gt;, &lt;code&gt;info&lt;/code&gt;, &lt;code&gt;graph --dot&lt;/code&gt;, &lt;code&gt;fmt&lt;/code&gt;, and &lt;code&gt;index&lt;/code&gt; — against a shared example bundle, and the C# CLI is diffed byte-for-byte against every one of them in CI. As of today the full suite passes end-to-end, including five byte-exact golden CLI comparisons against the original captures.&lt;/p&gt;

&lt;p&gt;I'll say the quiet part out loud: this port was AI-assisted, done largely with Claude Code driving the migration file by file, spec section by spec section, with the golden fixtures as the ground truth it had to match exactly. I think that's worth stating plainly rather than glossing over — a byte-exact port across languages is a fairly mechanical, well-specified translation task with an unambiguous pass/fail signal (does the output match the captured bytes, yes or no), which is exactly the kind of task where an AI pair-programmer earns its keep and where you can trust the result &lt;em&gt;because&lt;/em&gt; you can verify it byte-for-byte rather than having to take anyone's word for it. The interesting design decisions — the YAML subset, the permissive-loading philosophy, the two-tier validation split — came from following the spec and the Python reference implementation; the AI assistance was in the grinding, get-every-byte-right execution, not the architecture.&lt;/p&gt;
&lt;h2&gt;
  
  
  Show, don't tell
&lt;/h2&gt;

&lt;p&gt;Here's the library, loading a bundle and running a conformance check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;OKF4net&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;bundle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"./my_bundle"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; concepts"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Conformance check (§9).&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BundleValidator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsConformant&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"conformant with OKF v&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;OkfSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Traverse the cross-link graph.&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ConceptId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tables/orders"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LinksFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Target&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (exists: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exists&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Bundle.Load&lt;/code&gt; never aborts on a malformed concept file — it collects parse failures into &lt;code&gt;bundle.ParseErrors&lt;/code&gt; and keeps walking the tree, because a knowledge base that one bad file can take down entirely is a bad knowledge base.&lt;/p&gt;

&lt;p&gt;And here's the CLI, which is the same tool the Rust binary used to be, invocation-for-invocation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;okf validate ./bundles/ga4
okf graph ./bundles/ga4 &lt;span class="nt"&gt;--dot&lt;/span&gt; | dot &lt;span class="nt"&gt;-Tsvg&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; graph.svg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;okf validate&lt;/code&gt; exits non-zero on a non-conformant bundle, so it drops straight into a CI step. The CLI ships as a self-contained, Native AOT single-file binary — no .NET runtime install required on the machine that runs it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The agents angle
&lt;/h2&gt;

&lt;p&gt;The reason I care about this format enough to port a whole library for it is &lt;code&gt;OKF4net.Agents&lt;/code&gt;, which turns an OKF bundle into tools and context for the &lt;a href="https://github.com/microsoft/agent-framework" rel="noopener noreferrer"&gt;Microsoft Agent Framework&lt;/a&gt;. &lt;code&gt;OkfBundleTools&lt;/code&gt; wraps one bundle root and exposes nine function tools — read, browse, graph, search, write, append-log, regenerate-indexes, validate, changes-since — that an &lt;code&gt;AIAgent&lt;/code&gt; can call directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OkfBundleTools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"./my_bundle"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;AIAgent&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsAIAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetTools&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RunAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Search the bundle for concepts about refunds."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Layer &lt;code&gt;OkfContextProvider&lt;/code&gt; onto the same tools instance and, opted in explicitly, an agent's exchanges get captured as long-term memory — one markdown concept per UTC day, written through the same validated, lock-protected write path the tools use, plus a matching &lt;code&gt;log.md&lt;/code&gt; entry. That's the part I think is genuinely different from the usual answer to "give my agent memory": instead of an opaque vector store you can't audit, memory is a markdown file in a git-tracked directory. You can open it, diff it across commits, redact a line, or point a second agent at the exact same directory with no export step. It's not a fit for every use case — the README is upfront that v1 memory is bundle-global and unscoped, so it's opt-in and meant for a shared, non-sensitive bundle rather than a multi-tenant deployment — but for a single team's shared knowledge base, "memory you can &lt;code&gt;git blame&lt;/code&gt;" is a real capability, not a slogan.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design choices
&lt;/h2&gt;

&lt;p&gt;The whole library — &lt;code&gt;OKF4net&lt;/code&gt; and &lt;code&gt;OKF4net.Cli&lt;/code&gt; — has zero third-party runtime dependencies: no YAML library, no CLI-parsing package, nothing. It has its own documented YAML &lt;em&gt;subset&lt;/em&gt; parser (frontmatter is scalars, lists, and shallow maps — no anchors, no tags, no multi-document files, and it says so with a clear error if you hand it those), its own markdown link scanner, and its own argument parsing, all on top of the .NET base class library. That constraint is what makes the CLI publishable as a single-file Native AOT binary with no runtime to install, and it's what keeps the barrier to contributing low — there's no framework to learn before you can read the code. &lt;code&gt;OKF4net.Agents&lt;/code&gt; is the one exception, since talking to &lt;code&gt;Microsoft.Agents.AI&lt;/code&gt; requires depending on it; everything else stays dependency-free by design, enforced project by project. The project also ships &lt;code&gt;OKF4net.Catalog&lt;/code&gt;, a local multi-bundle catalog with search-by-source resolution, and &lt;code&gt;OKF4net.Mcp&lt;/code&gt;, an MCP server that plugs a bundle straight into Claude Desktop or Claude Code — so agents and tools have a ready path to discover and query bundles without writing that plumbing themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Come contribute
&lt;/h2&gt;

&lt;p&gt;OKF4net is young and I'd rather it stay welcoming than gate-kept. If you'd like to start by reading, the &lt;a href="https://jchable.github.io/okf4net/" rel="noopener noreferrer"&gt;project site and docs&lt;/a&gt; are the friendlier entry point — a guided overview and getting-started walkthroughs.&lt;br&gt;
You don't need any prior OKF knowledge to help — the &lt;a href="https://github.com/jchable/okf4net/labels/good%20first%20issue" rel="noopener noreferrer"&gt;&lt;code&gt;good first issue&lt;/code&gt;&lt;/a&gt; label names the files to touch and the test that should go green when you're done, &lt;a href="https://github.com/jchable/okf4net/blob/main/ROADMAP.md" rel="noopener noreferrer"&gt;&lt;code&gt;ROADMAP.md&lt;/code&gt;&lt;/a&gt; lays out where the project is headed, and &lt;a href="https://github.com/jchable/okf4net/discussions" rel="noopener noreferrer"&gt;Discussions&lt;/a&gt; is the place to ask a question before you write any code. The project is licensed &lt;strong&gt;LGPL-3.0-or-later&lt;/strong&gt;, and the bar to your first PR is exactly three commands: &lt;code&gt;dotnet build&lt;/code&gt;, &lt;code&gt;dotnet test&lt;/code&gt;, &lt;code&gt;dotnet format&lt;/code&gt;. If any part of "knowledge bundles you can &lt;code&gt;cat&lt;/code&gt; and agents that remember things in files you can read" sounds useful to you, I'd love the help — and the feedback.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>opensource</category>
      <category>softwaredevelopment</category>
    </item>
  </channel>
</rss>
