<?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: Paul Chen</title>
    <description>The latest articles on DEV Community by Paul Chen (@paul_chen_90371fe7426cb44).</description>
    <link>https://dev.to/paul_chen_90371fe7426cb44</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%2F3898190%2F38f30ba0-280f-4a52-8c7d-c773315b8da8.jpg</url>
      <title>DEV Community: Paul Chen</title>
      <link>https://dev.to/paul_chen_90371fe7426cb44</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/paul_chen_90371fe7426cb44"/>
    <language>en</language>
    <item>
      <title>Harnessing the Nondeterministic Machine: Practical Lessons in Agentic AI Hardening</title>
      <dc:creator>Paul Chen</dc:creator>
      <pubDate>Fri, 16 Oct 2026 18:17:31 +0000</pubDate>
      <link>https://dev.to/paul_chen_90371fe7426cb44/harnessing-the-nondeterministic-machine-practical-lessons-in-agentic-ai-hardening-8bo</link>
      <guid>https://dev.to/paul_chen_90371fe7426cb44/harnessing-the-nondeterministic-machine-practical-lessons-in-agentic-ai-hardening-8bo</guid>
      <description>&lt;p&gt;There is a specific kind of confidence failure that shows up in agentic AI systems. It is not the dramatic hallucination - the confident fabrication of a person who never existed. It is quieter than that. The model understands the task. It understands the data. And then it decides it has a better idea.&lt;/p&gt;

&lt;p&gt;We ran into this repeatedly while building the content health maintenance workflow for &lt;a href="https://github.com/axoviq-ai/synthadoc" rel="noopener noreferrer"&gt;Synthadoc&lt;/a&gt;, an open-source AI-native wiki engine. The workflow's job sounds modest: scan wiki pages for structural problems like duplicate headings or thin sections, propose targeted fixes, and let the user approve them before applying anything. What we found, and eventually fixed, is a catalogue of ways an agent can go wrong even when the model is capable and the intent is clear - and why the solution is never just a better instruction.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem We Were Solving
&lt;/h2&gt;

&lt;p&gt;Synthadoc ingests raw documents - PDFs, web pages, transcripts, spreadsheets - and uses an LLM to synthesize them into a structured Markdown wiki. Over time, the same topic gets ingested from multiple sources. Without intervention, a single wiki page about Alan Turing can accumulate ten copies of &lt;code&gt;## Bletchley Park and Cryptanalysis&lt;/code&gt; and eight copies of &lt;code&gt;## Early Life and Education&lt;/code&gt;. The page is coherent in isolation but structurally broken: duplicate content, contradictory emphasis, and thin sections that exist only because a heading was created before the body text was written.&lt;/p&gt;

&lt;p&gt;The content health scanner addresses this. It is deliberately a pure-Python heuristic with no LLM dependency - it detects &lt;code&gt;duplicate_section&lt;/code&gt;, &lt;code&gt;empty_section&lt;/code&gt;, &lt;code&gt;thin_section&lt;/code&gt;, &lt;code&gt;unclosed_code_block&lt;/code&gt;, &lt;code&gt;orphaned_heading&lt;/code&gt;, and &lt;code&gt;broken_list_nesting&lt;/code&gt; through deterministic regex. This part is reliable. The next part - using an LLM to fix the issues - is where the story gets interesting.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We Intended the Workflow to Do
&lt;/h2&gt;

&lt;p&gt;The design was explicit. For each flagged page:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Load the page content and the list of detected issues&lt;/li&gt;
&lt;li&gt;Send both to the LLM with a carefully constructed prompt&lt;/li&gt;
&lt;li&gt;Show the user a side-by-side BEFORE/AFTER diff&lt;/li&gt;
&lt;li&gt;Wait for approval before writing anything&lt;/li&gt;
&lt;li&gt;Snapshot the previous version so a rollback is possible&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every step in this design has a purpose. The BEFORE/AFTER review is a human gate, not a formality. The snapshot is a reversibility guarantee. The heuristic scanner runs before the LLM so the issue list is precise, not speculative.&lt;/p&gt;

&lt;p&gt;What actually happened when we first ran it told us exactly which parts of our hardening were missing.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Went Wrong
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Failure 1: The Model Rewrote the Page
&lt;/h3&gt;

&lt;p&gt;The first problem emerged immediately. The LLM received the page content and a list of issues. There were twelve structural problems on the page about the Open Source Movement - three duplicate sections and nine thin sections. The AFTER content we received back bore no structural resemblance to the original. The original heading &lt;code&gt;## Richard Stallman and GNU (1983)&lt;/code&gt; was gone. The heading &lt;code&gt;## The Cathedral and the Bazaar (1999)&lt;/code&gt; was gone. In their place: &lt;code&gt;## Historical Framing and Origins&lt;/code&gt;, &lt;code&gt;### Research Computing Culture and Principled Advocacy&lt;/code&gt;, &lt;code&gt;### GNU Toolchain and the Missing Kernel&lt;/code&gt;. The content was factually reasonable. It was also something the user never asked for and could not safely approve, because we had lost all traceability between the original and the proposed version.&lt;/p&gt;

&lt;p&gt;The model had, in effect, written its own wiki page from its pre-trained knowledge, using our wiki page as a rough reference. The word "fix" in our prompt was interpreted by the model as license for rewriting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Failure 2: The Model Kept the Wrong Duplicate
&lt;/h3&gt;

&lt;p&gt;For a page about computing history, the scanner flagged &lt;code&gt;## Moore's Law&lt;/code&gt; appearing three times. Two occurrences were thin summaries of about forty words each. The third was a detailed paragraph with four footnote citations to the source material. Our initial prompt said "keep the first occurrence." The model faithfully kept the first - one of the forty-word summaries - and discarded the version with citations.&lt;/p&gt;

&lt;p&gt;Technically correct. Practically, the wrong choice. We had a rule that was precise but not meaningful for the actual goal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Failure 3: CLI-Provider Artifacts in the Proposed Content
&lt;/h3&gt;

&lt;p&gt;Synthadoc supports multiple LLM backends. One of them is a CLI-based provider - an agentic tool (like opencode or Claude Code) that is itself an agent with its own tool-calling loop. When we routed the content health workflow through this provider, the response text included shell command output: &lt;code&gt;&amp;lt;|func_arg_name|&amp;gt;command&amp;lt;|func_arg_value|&amp;gt;cat /Users/user1/wikis/alan-turing.md&lt;/code&gt;. The model had read the actual file from disk, bypassing our carefully constructed context, and its internal tool invocations leaked into the response text we were about to write to the wiki.&lt;/p&gt;

&lt;h3&gt;
  
  
  Failure 4: Planning Notes Presented as Final Content
&lt;/h3&gt;

&lt;p&gt;In some runs, the proposed content contained lines like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight common_lisp"&gt;&lt;code&gt;&lt;span class="err"&gt;###&lt;/span&gt; &lt;span class="nv"&gt;LISP&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1958&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;actually&lt;/span&gt; &lt;span class="nv"&gt;LISP&lt;/span&gt; &lt;span class="nv"&gt;is&lt;/span&gt; &lt;span class="nv"&gt;more&lt;/span&gt; &lt;span class="nb"&gt;third&lt;/span&gt; &lt;span class="nv"&gt;gen&lt;/span&gt; &lt;span class="nv"&gt;but...&lt;/span&gt;
&lt;span class="err"&gt;###&lt;/span&gt; &lt;span class="nv"&gt;Introduction&lt;/span&gt; &lt;span class="nv"&gt;paragraph&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;Expand&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;50+&lt;/span&gt; &lt;span class="nv"&gt;words&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These were the model's internal planning notes - draft scaffolding that it had not cleaned up before terminating its response. These notes would have been written to the wiki without detection. It is possible that a user reviewing a diff in a modal at speed might miss them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Failure 5: Heading Renamed to "Correct" a Date
&lt;/h3&gt;

&lt;p&gt;After tightening the prompt, one run still returned a subtle violation. The original heading was &lt;code&gt;## The Cathedral and the Bazaar (1999)&lt;/code&gt;. The proposed content had &lt;code&gt;## The Cathedral and the Bazaar&lt;/code&gt; - the year dropped. The model "knew" the essay was presented in 1997 and silently corrected what it interpreted as an error in the user's own heading. It was an editorial judgment we had not authorized.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Failure Mode Map
&lt;/h2&gt;

&lt;p&gt;These failures cluster into two categories that are worth naming precisely.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqccll5yuihps5al7oe5q.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqccll5yuihps5al7oe5q.jpg" alt="agentic workflow" width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Category A - DRIFT: Model understands task but applies wrong judgment&lt;br&gt;
  Category B - HALLUCINATION: Model substitutes its own knowledge for the task&lt;/p&gt;

&lt;p&gt;It is important to distinguish. As a result of drift, the right process is being used to make wrong decisions - "keep the first" instead of "keep the best." As a result of hallucination, the right process is being replaced completely - generating a page from weights instead of fixing the one we hand it. Different mitigations are required for each.&lt;/p&gt;


&lt;h2&gt;
  
  
  How We Hardened the System and Built the Harness
&lt;/h2&gt;

&lt;p&gt;These fixes fall into two distinct layers. &lt;strong&gt;Hardening&lt;/strong&gt; addresses what the model produces: tighter prompts, output parsing, provider-specific guards. &lt;strong&gt;Harness engineering&lt;/strong&gt; addresses what the model can affect: human gates, rollback, scope exclusions. Hardening reduces the probability of a bad output; the harness limits the consequence of one that gets through. Both are required - hardening alone cannot guarantee zero failures, and a harness without hardening just moves the review burden to the human gate.&lt;/p&gt;
&lt;h3&gt;
  
  
  Hardening the Agent
&lt;/h3&gt;
&lt;h3&gt;
  
  
  1. Artifact Extraction as an Input Boundary
&lt;/h3&gt;

&lt;p&gt;The CLI-provider issue required a structural fix, not a prompt fix. We introduced &lt;code&gt;_extract_md_from_response()&lt;/code&gt; - a pre-processing step that strips everything before the first &lt;code&gt;#&lt;/code&gt; H1 heading in the response. CLI-provider tool call output always appears before the actual content begins. By treating the first H1 as the clean boundary, we recover the markdown content without the artifact noise.&lt;/p&gt;

&lt;p&gt;This is an example of boundary enforcement at the output interface: treat the model's raw response as untrusted input to a parser, not as a value ready to write.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Draft Detection as a Post-Output Gate
&lt;/h3&gt;

&lt;p&gt;For planning notes, we added &lt;code&gt;_is_draft()&lt;/code&gt; - a regex that catches linguistic markers of incomplete thinking: &lt;code&gt;TODO&lt;/code&gt;, &lt;code&gt;FIXME&lt;/code&gt;, &lt;code&gt;Expand to&lt;/code&gt;, &lt;code&gt;Hmm,&lt;/code&gt;, &lt;code&gt;Wait,&lt;/code&gt;, &lt;code&gt;I'll&lt;/code&gt;, &lt;code&gt;Let me&lt;/code&gt;, &lt;code&gt;existing content&lt;/code&gt;. If any match, the proposed content is flagged and the page is skipped automatically with a clear explanation in the UI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;_DRAFT_RE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^\s*(TODO|FIXME|Expand to\b|Hmm[,.\s]|Wait[,.\s]|I&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ll\b|I will\b|Let me\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;|existing content|combine existing|add more content|placeholder)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IGNORECASE&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MULTILINE&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;This is an output validation gate - a deterministic check applied after the model responds and before the result reaches the user approval screen. The model cannot disable it by rephrasing the note.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Surgical Prompt Constraints
&lt;/h3&gt;

&lt;p&gt;The original prompt said: "Fix the structural issues listed below." That sentence, to a language model, is nearly unlimited scope. We replaced it with an explicit constraint hierarchy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;ABSOLUTE RULES - violating any one is a failure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;• PRESERVE every existing section heading EXACTLY - same text, same&lt;/span&gt; &lt;span class="c1"&gt;# level,&lt;/span&gt;
    &lt;span class="s"&gt;including any dates or years in parentheses. Do not 'correct' headings.&lt;/span&gt;
  &lt;span class="s"&gt;• PRESERVE the section ORDER exactly as in the original.&lt;/span&gt;
  &lt;span class="s"&gt;• Do NOT add new top-level or second-level sections.&lt;/span&gt;
  &lt;span class="s"&gt;• Do NOT rename, merge, split, or reorder any sections.&lt;/span&gt;
  &lt;span class="s"&gt;• Do NOT alter factual claims in sections you are not fixing.&lt;/span&gt;

&lt;span class="s"&gt;PERMITTED CHANGES (only these, only where the issue list directs)&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;1. Duplicate sections&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keep the most complete occurrence (most words and/or&lt;/span&gt;
     &lt;span class="s"&gt;citations); delete the others. Place the kept version where the first&lt;/span&gt;
     &lt;span class="s"&gt;occurrence was. Do NOT merge or rewrite - keep it verbatim.&lt;/span&gt;
  &lt;span class="na"&gt;2. Thin/empty sections&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;add factual prose UNDER the existing heading.&lt;/span&gt;
     &lt;span class="s"&gt;The heading text must remain unchanged.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Changing from "fix issues" to "permitted changes" is intentional. The model cannot rationalize around a list of what it is allowed to do when permissions are enumerated rather than prohibitions.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Issue Count Threshold for Nondeterministic Providers
&lt;/h3&gt;

&lt;p&gt;Even with the tighter prompt, pages with more than six issues on a CLI-provider run produced hallucinated new sections. The CLI provider is itself an agentic tool with its own reasoning loop - it does not behave like a simple completion endpoint, and the compound nondeterminism (our model + its internal model) overwhelms a long issue list.&lt;/p&gt;

&lt;p&gt;We added a hard skip at six issues for the CLI path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# CLI providers (opencode, claude-code) are agentic - they read files,
# plan before writing, and hallucinate new sections when the issue list
# is long. API providers follow the prompt reliably and don't need this
# guard. This path is CLI-only, so the cap applies only there.
&lt;/span&gt;&lt;span class="n"&gt;_MAX_ISSUES_AUTO_FIX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;API-key providers (Anthropic, OpenAI) use a separate code path with no cap, since they are stateless completion endpoints that see the prompt exactly as we construct it. Rather than a model capability, this is a property of the architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building the Harness
&lt;/h3&gt;

&lt;h3&gt;
  
  
  5. System Page Exclusion
&lt;/h3&gt;

&lt;p&gt;Thin sections on pages named &lt;code&gt;index&lt;/code&gt; and &lt;code&gt;dashboard&lt;/code&gt; were correctly identified by the scanner. These are navigation pages, not content pages - thin sections are structurally appropriate. The workflow would propose expanding them without exclusion, which the user would reject correctly, wasting their time.&lt;/p&gt;

&lt;p&gt;We use the existing &lt;code&gt;SYSTEM_PAGE_SLUGS&lt;/code&gt; constant to exclude them from the fix loop. No duplicate definition, no magic string. The scanner still runs on them - the issues appear in lint reports - but the auto-fix workflow skips them.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. The Human Gate as a Structural Requirement
&lt;/h3&gt;

&lt;p&gt;Every proposed fix must pass through a BEFORE/AFTER diff review before anything is written. This is not a feature - it is the architectural guarantee that no automated process can corrupt wiki content without explicit consent. The confirm card shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The page slug and issue summary&lt;/li&gt;
&lt;li&gt;BEFORE (first 2000 chars of the original)&lt;/li&gt;
&lt;li&gt;AFTER (first 2000 chars of the proposed content)&lt;/li&gt;
&lt;li&gt;"Approve" / "Skip this page" buttons with a 5-minute timeout (auto-decline)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The scrollability of code blocks in the review modal was a concrete UX issue we had to address - users need to be able to read what they are approving. Gates that are theoretically present but practically unusable are not gates.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Snapshot and Rollback as a Safety Net
&lt;/h3&gt;

&lt;p&gt;Before any approved fix is written, &lt;code&gt;snapshot_if_changed()&lt;/code&gt; takes a versioned copy of the original content. If the approved result turns out to be wrong, the user can request a rollback by name: "roll back alan-turing." The workflow restores the snapshot, re-runs the scanner, and updates the issue list to reflect the restored state. It is not a rollback to recover from system failure - it is a first-class workflow feature that acknowledges the model may produce something subtly wrong that was not detected during review.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Broader Principle: Both Layers Are Architectural, Not Textual
&lt;/h2&gt;

&lt;p&gt;The pattern we followed across all of these fixes is consistent: every control - whether it hardens the agent's output or constrains the harness around it - is implemented in code, not in prose.&lt;/p&gt;

&lt;p&gt;"Do not hallucinate" in a system prompt is a behavioral suggestion. The model considers it, weighs it against other pressures, and sometimes overrides it. That is not a flaw, it is how language models work. The token prediction process does not have a hard stop for "this would be a hallucination."&lt;/p&gt;

&lt;p&gt;Both hardening and harness engineering mean building controls that are not subject to the model's interpretation:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Control&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Why it cannot be bypassed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hardening&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_extract_md_from_response()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Strips pre-H1 content&lt;/td&gt;
&lt;td&gt;Runs on raw response bytes, before any model interpretation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hardening&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;_is_draft()&lt;/code&gt; regex&lt;/td&gt;
&lt;td&gt;Blocks planning-note content&lt;/td&gt;
&lt;td&gt;Deterministic regex match - not in the model's loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hardening&lt;/td&gt;
&lt;td&gt;PERMITTED CHANGES prompt&lt;/td&gt;
&lt;td&gt;Enumerates allowed scope&lt;/td&gt;
&lt;td&gt;Narrows the action space; drift must contradict the list&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hardening&lt;/td&gt;
&lt;td&gt;Issue count cap&lt;/td&gt;
&lt;td&gt;Skips complex pages&lt;/td&gt;
&lt;td&gt;Hard code gate; model never sees the page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Harness&lt;/td&gt;
&lt;td&gt;System page exclusion&lt;/td&gt;
&lt;td&gt;Skips non-content pages&lt;/td&gt;
&lt;td&gt;Slug lookup against a constant; not in the prompt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Harness&lt;/td&gt;
&lt;td&gt;BEFORE/AFTER human gate&lt;/td&gt;
&lt;td&gt;Requires user approval&lt;/td&gt;
&lt;td&gt;No write path bypasses this&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Harness&lt;/td&gt;
&lt;td&gt;Snapshot / rollback&lt;/td&gt;
&lt;td&gt;Preserves prior state&lt;/td&gt;
&lt;td&gt;Written before the fix is applied&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These controls form layers. A fix that evades the prompt constraints is caught by the draft detector or the human gate. A fix that passes the human gate can be rolled back. No single layer is sufficient. The layers together are.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Matters Beyond Wiki Maintenance
&lt;/h2&gt;

&lt;p&gt;Incorrectly updating a wiki page is a recoverable mistake. A user clicks rollback, the page is restored, and no lasting harm is done. But the failure modes we observed - a model substituting its own knowledge for the task, a model including its internal tool calls in the output, a model silently correcting data it considered wrong - are not domain-specific. The same failure patterns appear in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agentic coding tools that refactor code the user did not ask to touch&lt;/li&gt;
&lt;li&gt;Document automation systems that "improve" a legal clause beyond the approved scope&lt;/li&gt;
&lt;li&gt;Data pipelines where an LLM normalizes values using world knowledge rather than the provided schema&lt;/li&gt;
&lt;li&gt;Financial report generators that extrapolate figures not present in the source data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In each case, the consequence of unhardened agentic behavior scales with the stakes of the domain. A hallucinated section in a personal wiki is recoverable. A hallucinated figure in a financial disclosure is not.&lt;/p&gt;

&lt;p&gt;The argument for hardening is not that models are bad. It is that models are nondeterministic, and production systems require deterministic guarantees at the boundaries where AI output meets persistent state.&lt;/p&gt;




&lt;h2&gt;
  
  
  What to Apply From This
&lt;/h2&gt;

&lt;p&gt;If you are building an agentic system that reads and modifies persistent data, consider these as minimum requirements:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Boundary enforcement.&lt;/strong&gt; Treat the model's output as untrusted input at every handoff. Parse it. Validate it. Strip what does not belong before it reaches the next step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deterministic post-output gates.&lt;/strong&gt; Add validation logic that runs after the model responds and is not subject to the model's behavior. Draft detection, schema validation, output format checks. These are deterministic; the model cannot reason around them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enumerated permissions over enumerated prohibitions.&lt;/strong&gt; In prompts, tell the model what it is allowed to do, not only what it must avoid. The scope of permitted actions is easier to reason about than an open-ended task with a list of exceptions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Acknowledge provider heterogeneity.&lt;/strong&gt; Not all "model" configurations behave the same way. A CLI-based agentic provider and a stateless API completion endpoint have fundamentally different behavioral properties. Design your hardening layer to account for both, or constrain which providers can reach which actions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Human gates as structural requirements.&lt;/strong&gt; For any workflow that modifies persistent state, a human review step is not a UX nicety - it is a control. Make it usable. If the review UI cannot display the full diff, the gate is not functioning as intended.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reversibility as a first-class design constraint.&lt;/strong&gt; Snapshots and rollback should be designed in from the start, not added after the first production incident. Assume the model will sometimes produce a subtly wrong result that passes review, and design the recovery path accordingly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;It took multiple iterations for Synthadoc's content health maintenance workflow to behave predictably. Rather than rephrasing the prompt, each iteration addressed a specific failure mode. As a result, the team trusts the workflow - not because the model is perfect, but because the system is designed to catch and contain errors.&lt;/p&gt;

&lt;p&gt;Synthadoc is open source. The content health scanner, the resolver workflow, and the draft detection logic are all available at &lt;a href="https://github.com/axoviq-ai/synthadoc" rel="noopener noreferrer"&gt;Synthadoc git repo&lt;/a&gt;. If you are building AI-native systems that write back to persistent state, the patterns described here are worth examining in the context of real code, not just as abstractions.&lt;/p&gt;

&lt;p&gt;The nondeterministic machine can be harnessed. The harness is engineering, not prompting.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
      <category>automation</category>
    </item>
    <item>
      <title>Four Platforms for Developer Writing: What Each One Does Best</title>
      <dc:creator>Paul Chen</dc:creator>
      <pubDate>Sun, 27 Sep 2026 18:26:36 +0000</pubDate>
      <link>https://dev.to/paul_chen_90371fe7426cb44/four-platforms-for-developer-writing-what-each-one-does-best-4b9m</link>
      <guid>https://dev.to/paul_chen_90371fe7426cb44/four-platforms-for-developer-writing-what-each-one-does-best-4b9m</guid>
      <description>&lt;h1&gt;
  
  
  Four Platforms for Developer Writing: What Each One Does Best
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;I've published on LinkedIn, Medium, Dev.to, and CoderLegion. People regularly ask me which platform to use. The honest answer: it depends on what you're trying to accomplish. Here's how I think about each one.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  LinkedIn - Entry Point, Not Destination
&lt;/h2&gt;

&lt;p&gt;LinkedIn is best used as a conversation starter. Put a provocative question, a key takeaway, or a short observation in the post, then include the link to the full article in the first comment. This uses LinkedIn's strengths - professional reach, cross-border visibility - without asking it to do what it's bad at: hosting deep technical content. Think of it as the top of a funnel, not where the real discussion takes place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Medium - for Storytelling and Long-Form Thought Leadership
&lt;/h2&gt;

&lt;p&gt;Medium is ideal for writing that's meant to endure: career reflections, architectural post-mortems, in-depth essays. The Partner Program adds a monetisation path. The main challenge is discoverability, posts get buried quickly unless you're already in a publication or have an existing audience driving traffic. Medium rewards writers who bring their own readers; it does little to surface new voices on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dev.to - for Tutorials, Community Discussion, and Learning in Public
&lt;/h2&gt;

&lt;p&gt;Dev.to is one of the most developer-focused writing platforms available. It's open, free, and has a genuinely engaged community that spans everything from students to senior engineers. The tag system makes technical content easy to find, and posts are indexed quickly by search engines, which gives your writing a longer shelf life than most social feeds.&lt;/p&gt;

&lt;p&gt;The real strength is the format fit: "how I built X," debugging stories, beginner tutorials, TIL posts, and learning-in-public threads all feel natural here. Series support lets you structure multi-part content cleanly. Cross-posting with a canonical URL is well-supported, so you can syndicate from your own blog without SEO penalty.&lt;/p&gt;

&lt;p&gt;The trade-off is volume - the platform is active, so standing out requires either strong tags, an early engagement spike, or consistent posting over time. That said, the comments section tends to attract thoughtful responses from people who actually read the post, which is not something you can take for granted elsewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  CoderLegion - for Early Visibility Without an Existing Audience
&lt;/h2&gt;

&lt;p&gt;CoderLegion's Trending and Promotion tags give newer posts and newer members real home-page visibility from day one, which most platforms don't offer. People tend to actually read and comment rather than scroll past, which makes it a good place to get early feedback on an idea before you've built an audience.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Developer's Journey&lt;/strong&gt; sharing group is worth highlighting specifically: it's designed for developers at any level to share progress and learn from each other's experiences, which creates a supportive environment that's harder to find on larger platforms.&lt;/p&gt;

&lt;p&gt;If you're just starting out or want genuine peer feedback rather than chasing an algorithm, CoderLegion is worth trying.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Rough Rule of Thumb
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Entry point / professional amplifier&lt;/td&gt;
&lt;td&gt;LinkedIn&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Technical tutorials, how-tos, learning in public&lt;/td&gt;
&lt;td&gt;Dev.to&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long-form essays, thought leadership&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Community feedback + early visibility&lt;/td&gt;
&lt;td&gt;CoderLegion&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Most experienced developers end up cross-posting to two or three. Write once, adapt the framing, post everywhere that fits.&lt;/p&gt;




&lt;h2&gt;
  
  
  Your thoughs
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Where do you prefer to publish?&lt;/em&gt; I'm curious which platform gives you the most useful engagement, drop it in the comments.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>socialmedia</category>
      <category>dev</category>
      <category>developers</category>
    </item>
    <item>
      <title>30 Domain Templates for Your LLM Knowledge Base: Skip the Setup, Start Capturing</title>
      <dc:creator>Paul Chen</dc:creator>
      <pubDate>Tue, 15 Sep 2026 01:17:15 +0000</pubDate>
      <link>https://dev.to/paul_chen_90371fe7426cb44/30-domain-templates-for-your-llm-knowledge-base-skip-the-setup-start-capturing-3g2e</link>
      <guid>https://dev.to/paul_chen_90371fe7426cb44/30-domain-templates-for-your-llm-knowledge-base-skip-the-setup-start-capturing-3g2e</guid>
      <description>&lt;p&gt;The hardest part isn't the LLM layer&lt;/p&gt;

&lt;p&gt;The hardest part of building an LLM knowledge base for a specific domain isn't wiring up the models, it's encoding how that domain thinks. A compliance analyst's knowledge base needs every obligation mapped to a specific regulation, every control to an owner, every exception to an approver. A clinical wiki needs evidence grades on every claim. A DevOps runbook system needs incident timelines and rollback steps. The query routing, intake form fields, and quality standards are all different — and none of that comes pre-configured.&lt;/p&gt;

&lt;p&gt;Most teams spend the first two weeks not capturing knowledge but arguing about structure: what pages should exist, how routing should decompose, which source types need their own forms. By the time those questions are settled, content has already been ingested into the wrong shape and needs to be redone.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;synthadoc install --template&lt;/code&gt; solves this in one command.&lt;/p&gt;




&lt;h2&gt;
  
  
  What a Domain Template Actually Provides
&lt;/h2&gt;

&lt;p&gt;A domain template is not a demo or a sample dataset. It is a pre-made structural skeleton for a specific knowledge domain - everything that governs how knowledge is organized, ingested, and queried, without any of the actual content.&lt;/p&gt;

&lt;p&gt;One install command gives you six structural components:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Query routing table (&lt;code&gt;ROUTING.md&lt;/code&gt;)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The map that tells the query engine which branches of your wiki to search based on the nature of the question. A finance/investment template routes "capital structure questions" to debt financing and covenant pages, while routing "valuation questions" to financial models and comparable companies. Writing this from scratch requires domain knowledge most developers building a knowledge system do not have — and the gap shows up as poor retrieval quality until someone with that expertise intervenes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Domain-tuned agent guidelines&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each template ships with &lt;code&gt;CLAUDE.md&lt;/code&gt;, &lt;code&gt;AGENTS.md&lt;/code&gt;, and &lt;code&gt;GEMINI.md&lt;/code&gt; in the wiki root — the standard files that AI coding assistants (Claude Code, Gemini CLI, GitHub Copilot Agents) read automatically when active in a directory. The domain guidelines are injected into each file, so any AI tool you already use becomes domain-aware the moment it opens the wiki.&lt;/p&gt;

&lt;p&gt;Here is what the clinical healthcare template puts in those files:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Cite clinical guidelines by issuing body, title, year, and recommendation strength (Grade A/B/C or Level 1/2/3). Note evidence quality for every clinical claim: RCT, meta-analysis, cohort study, expert consensus, or case report. Never store identifiable patient data in wiki pages.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Compare that to the compliance template:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Map every compliance obligation to the specific regulation, rule, or contractual requirement that creates it. Track controls with the control owner, testing frequency, and last test date. Distinguish between legal requirements (non-negotiable) and internal policy requirements (adjustable). Note jurisdiction and regulated entity for every obligation.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When a compliance analyst has Claude Code running in their wiki directory and asks it to help write a page or interpret a lint warning, it already knows that regulatory citations need jurisdiction-level specificity and that control owners must be named — without configuring anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Scaffold stub pages&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;8–12 stub pages covering the core knowledge areas of the domain — empty frames with the right titles, frontmatter, and a &lt;code&gt;&amp;lt;!-- synthadoc:scaffold --&amp;gt;&lt;/code&gt; zone marker that separates content you write (preserved) from content the weekly scaffold job refreshes (generated). The structural skeleton maintains itself without ever overwriting your edits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Blank intake forms&lt;/strong&gt; (&lt;code&gt;template-*.md&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;Structured markdown forms in &lt;code&gt;raw_sources/&lt;/code&gt; for each document type common to the domain. A finance/investment install includes forms for deal memos, company profiles, and due diligence checklists — the same fields a professional would populate in any standard memo, now wired directly into the ingest pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Pre-validated seed sources (&lt;code&gt;seeds.md&lt;/code&gt;)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Curated ingest commands for public-domain reference sources - all checked for accessibility and domain appropriateness before the template was released. For finance/investment, these include SEC investor education materials and Federal Reserve H.15 interest rate releases. For healthcare/clinical, clinical guideline registries and evidence-based medicine references. Real domain context before your proprietary content lands.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Scheduled maintenance jobs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A weekly lint run (Sunday 2:00 AM) and a weekly scaffold job (Sunday 3:00 AM), pre-registered at install time. Neither requires manual intervention once the wiki is running.&lt;/p&gt;




&lt;h2&gt;
  
  
  Template Library: 30 Domains, 9 Categories
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F88iu02g3hpcebgr5q6x7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F88iu02g3hpcebgr5q6x7.png" alt="domain templates" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The selection reflects domains where knowledge is both high-stakes (wrong answers have real consequences) and naturally document-heavy (the source material already exists - it is just not queryable). Finance, healthcare, and legal are the clearest examples. Technology and research are the next tier, where the cost of an engineer not knowing the current incident runbook, or a researcher duplicating a failed experiment, is concrete and measurable.&lt;/p&gt;




&lt;h2&gt;
  
  
  From Install to First Query
&lt;/h2&gt;

&lt;p&gt;Getting a domain-template wiki to the point where it can answer real questions takes less than an hour.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Install&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;synthadoc templates list          &lt;span class="c"&gt;# browse all 30 options&lt;/span&gt;
synthadoc &lt;span class="nb"&gt;install &lt;/span&gt;my-wiki &lt;span class="nt"&gt;--target&lt;/span&gt; ~/wikis &lt;span class="nt"&gt;--template&lt;/span&gt; finance/investment
synthadoc serve my-wiki
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The install creates a standard wiki then applies the template: routing table, agent guidelines, stub pages, intake forms, and seeds. Staging is enabled by default — every ingested page goes to a candidates queue for review before entering the live wiki.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Ingest seed sources and your own documents&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# From seeds.md — authoritative public reference material&lt;/span&gt;
synthadoc ingest &lt;span class="s2"&gt;"https://www.investor.gov/introduction-investing"&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; my-wiki

&lt;span class="c"&gt;# Your own sources — batch ingest everything in raw_sources/ at once&lt;/span&gt;
synthadoc ingest raw_sources/ &lt;span class="nt"&gt;--batch&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; my-wiki
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3: Review candidates in Obsidian&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Open the Obsidian plugin and run &lt;strong&gt;Synthadoc: Candidates: review candidate pages...&lt;/strong&gt; from the Command Palette. The modal lists every staged page with its slug, colour-coded confidence badge (&lt;code&gt;high&lt;/code&gt; / &lt;code&gt;medium&lt;/code&gt; / &lt;code&gt;low&lt;/code&gt;), and ingest timestamp — promote what passes your bar, discard what does not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Run lint&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;synthadoc lint run &lt;span class="nt"&gt;-w&lt;/span&gt; my-wiki
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lint validates every page against the domain guidelines, checks citations, and flags orphans and contradictions. Clean draft pages are promoted to &lt;code&gt;active&lt;/code&gt; automatically. After lint, your wiki is queryable.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Happens After
&lt;/h2&gt;

&lt;p&gt;Once the wiki is active, maintenance is driven by the web UI rather than by you remembering to check things. After every query or lint run, the engine reads its own output and pre-fills the input box with the highest-priority next action — if it found stale pages, the prompt says "Re-ingest these pages"; if re-ingest just finished, it says "Run lint to promote them"; if contradictions surfaced, it queues the resolver. You confirm and it runs. No command to remember, no separate dashboard to check.&lt;/p&gt;

&lt;p&gt;Behind that interface, the agentic layer handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Contradiction resolution&lt;/strong&gt; — conflicting pages are identified, original sources retrieved, resolution proposed interactively&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-ingest on source updates&lt;/strong&gt; — stale pages refreshed with a before/after diff for your approval&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Broken wikilink repair&lt;/strong&gt; — dead links scanned and repaired automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Citation integrity&lt;/strong&gt; — claims audited against source documents; drift flagged for re-ingest&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sensitive data handling&lt;/strong&gt; — sanitizer strips PII at ingest time; &lt;code&gt;synthadoc retract&lt;/code&gt; rolls back a source and all pages citing it if something slips through&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduled lint and scaffold&lt;/strong&gt; — weekly jobs keep the knowledge graph healthy without intervention&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal: after the first few days of populating your wiki, the agentic layer handles the maintenance work that would otherwise require a dedicated knowledge engineer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Reference
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;synthadoc templates list                          &lt;span class="c"&gt;# browse the library&lt;/span&gt;
synthadoc &lt;span class="nb"&gt;install&lt;/span&gt; &amp;lt;name&amp;gt; &lt;span class="nt"&gt;--template&lt;/span&gt; &amp;lt;&lt;span class="nb"&gt;cat&lt;/span&gt;/domain&amp;gt;  &lt;span class="c"&gt;# install with a template&lt;/span&gt;
synthadoc templates &lt;span class="nb"&gt;sync&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; &amp;lt;wiki&amp;gt;                &lt;span class="c"&gt;# sync after an upgrade&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://github.com/axoviq-ai/synthadoc/releases/tag/v1.3.3" rel="noopener noreferrer"&gt;Release notes (v1.3.3)&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full template docs:&lt;/strong&gt; &lt;a href="https://github.com/axoviq-ai/synthadoc/blob/main/synthadoc/templates/README.md" rel="noopener noreferrer"&gt;template/readme&lt;/a&gt; in the installed package&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;&lt;a href="https://github.com/axoviq-ai/synthadoc" rel="noopener noreferrer"&gt;Synthadoc&lt;/a&gt; is an open-source LLM knowledge compilation engine, it ingests your sources, compiles them into a queryable, citation-backed wiki, and keeps it consistent and self-growing over time. The domain template library ships in every install, no additional packages required.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>llm</category>
      <category>agents</category>
      <category>automation</category>
      <category>domain</category>
    </item>
    <item>
      <title>Hallucination Is an Architecture Problem, Not Only a Prompt Problem</title>
      <dc:creator>Paul Chen</dc:creator>
      <pubDate>Fri, 28 Aug 2026 19:06:47 +0000</pubDate>
      <link>https://dev.to/paul_chen_90371fe7426cb44/hallucination-is-an-architecture-problem-not-only-a-prompt-problem-51p8</link>
      <guid>https://dev.to/paul_chen_90371fe7426cb44/hallucination-is-an-architecture-problem-not-only-a-prompt-problem-51p8</guid>
      <description>&lt;p&gt;If you've built a knowledge base on top of an LLM, you've probably noticed something counterintuitive: the outputs rarely contain flat-out fabrications. What they contain is &lt;em&gt;drift&lt;/em&gt; — the model summarizes accurately but slightly overstates a confidence level, rounds a number, or drops a qualifier. "As of 2023" quietly becomes "currently." A causal relationship is inferred from correlation.&lt;/p&gt;

&lt;p&gt;These are small errors individually. Across a knowledge base that gets queried repeatedly, they compound.&lt;/p&gt;

&lt;p&gt;Prompt instructions help. &lt;em&gt;Don't make things up, answer only from the provided context, never fabricate&lt;/em&gt; — these do nudge model behavior in the right direction. We use them too. But they leave enforcement entirely up to the model doing the generating. A language model producing text token-by-token has no reliable internal mechanism to audit its own faithfulness to a source.&lt;/p&gt;

&lt;p&gt;We built Synthadoc — an LLM-based knowledge base compiler — around a different bet: &lt;strong&gt;hallucination is an architecture problem, not only a prompt problem.&lt;/strong&gt; A prompt fix is hoping the model behaves. An architecture fix is verifying that it did.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Layers
&lt;/h2&gt;

&lt;p&gt;The approach has three distinct layers, each targeting a different failure mode:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Domain scoping at ingest&lt;/strong&gt; — Only on-domain content enters the knowledge base. Out-of-scope material never becomes a wiki page, so it can never become the basis for an answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retrieval-grounded generation&lt;/strong&gt; — Answers are built from retrieved pages with an explicit positive constraint: answer from &lt;em&gt;only&lt;/em&gt; these pages, cite every claim. Positive constraints ("do only Y") are harder to rationalize around than negative ones ("don't do X").&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Citation faithfulness audit&lt;/strong&gt; — A separate LLM pass examines each claim against the source lines it cites and classifies the relationship.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of these is a complete solution alone. Together they make hallucination &lt;em&gt;detectable and traceable&lt;/em&gt; rather than just discouraged.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 1: Content Gate at Ingest
&lt;/h2&gt;

&lt;p&gt;Every wiki in Synthadoc has a &lt;code&gt;purpose.md&lt;/code&gt; that defines its domain. When a new source arrives, the ingest agent reads this before deciding what to do with it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wiki scope (from purpose.md):
{content of purpose.md}

action="skip" means the source is completely OUTSIDE the wiki's domain.
…
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent then chooses: &lt;code&gt;skip&lt;/code&gt;, &lt;code&gt;create&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt;, or &lt;code&gt;flag&lt;/code&gt;. Off-domain content is dropped at the door.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When sources conflict&lt;/strong&gt;, the flag path runs: the ingest agent issues a &lt;code&gt;flag&lt;/code&gt; action and the page moves to a &lt;code&gt;contradicted&lt;/code&gt; lifecycle state, automatically excluded from retrieval until the conflict is resolved. A contradicted page is one where the system can't be confident what's true — serving answers from it would be hallucination with extra steps.&lt;/p&gt;

&lt;p&gt;Resolving a contradiction requires an explicit human approval. An interactive agentic workflow surfaces the conflict, shows a diff of the proposed edit, and blocks writing until a human accepts it. The gate is unconditional.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 2: Grounded Generation
&lt;/h2&gt;

&lt;p&gt;At query time, Synthadoc retrieves the most relevant pages using hybrid search (BM25 + optional vector re-ranking) and builds a synthesis prompt that includes those pages verbatim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Answer using ONLY these wiki pages. Cite with [[PageTitle]].
Extract and include all specific facts — dates, years, numbers, and names —
even when they appear briefly or in passing.

Question: {question}
Pages: {retrieved_wiki_pages}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;purpose.md&lt;/code&gt; domain description is prepended to the pages block as a reminder during synthesis.&lt;/p&gt;

&lt;p&gt;When retrieval confidence falls below threshold, the system surfaces the gap rather than guessing. The response notes that the wiki lacks dedicated coverage on that topic and generates enrichment suggestions — search queries and domain-scoped URLs. Any pages retrieved flow through the normal ingest pipeline and stay in &lt;code&gt;draft&lt;/code&gt; state, out of the search corpus, until a human promotes them to &lt;code&gt;active&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What grounding doesn't solve:&lt;/strong&gt; A model can still hallucinate &lt;em&gt;within&lt;/em&gt; retrieved content — misattributing which page said what, inventing a specific number close to but not the same as the source, or synthesizing a conclusion that no single page actually states. Grounding reduces the search space. It doesn't eliminate drift.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 3: Citation Faithfulness Audit
&lt;/h2&gt;

&lt;p&gt;This is the layer that makes the system &lt;em&gt;verifiable&lt;/em&gt;. Each claim in an answer carries a citation marker linking it to specific source lines. A separate LLM pass then audits whether the claim is actually supported:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;supported&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The claim is directly and accurately supported by the cited source lines.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;drift&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The claim is related to the source but overstated, simplified, or shifted in meaning.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hallucination&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The source lines do not support the claim, or directly contradict it.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The auditor sees only the claim and the raw source lines — not the original question. This matters: the same model that wrote the answer is likely to defend it when asked "is this correct?" Separation of duties removes that incentive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we actually see:&lt;/strong&gt; &lt;code&gt;drift&lt;/code&gt; is the most common finding. Specific dates that don't appear in the source, an attribution switched between two people in the same paragraph, a qualifier silently dropped — these are the findings that surface. True &lt;code&gt;hallucination&lt;/code&gt;, where source lines don't support the claim at all, is less common in well-grounded RAG. But it happens, and when it does the audit tells you exactly which source line to check.&lt;/p&gt;

&lt;p&gt;Audit results are cached per-page with staleness detection. If a page is re-ingested from updated source material, the cache invalidates and the next audit reflects the new content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Things This Still Doesn't Solve
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Synthesis across pages.&lt;/strong&gt; A conclusion drawn by combining facts from multiple pages may be accurate at each step and still misleading in combination. The citation audit operates per-citation — cross-page inferences that no single page actually states fall outside its scope. The practical mitigation: cited pages are always explicit, so synthesized conclusions can be verified. And if a conclusion matters enough to keep, saving it as a wiki page subjects it to future source ingestion — any new source that disputes it triggers the &lt;code&gt;flag&lt;/code&gt; action.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Uniform source error.&lt;/strong&gt; When all available sources agree on the same wrong answer, no contradiction is detected and Synthadoc answers confidently from incorrect material. Two mechanisms reduce this risk: adversarial lint, which uses the model's general knowledge to flag claims that clearly contradict well-established facts; and re-ingesting from updated authoritative sources, which triggers the &lt;code&gt;flag&lt;/code&gt; action against outdated pages. Neither is a complete solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  The View From Here
&lt;/h2&gt;

&lt;p&gt;The hallucination debate is usually framed as a model capability question: will the next model be more faithful? For a production knowledge system, that's the wrong frame. Even a perfectly faithful model can only be as accurate as the content it's grounded in. And even a slightly drifting model can be made accountable if its output is systematically audited.&lt;/p&gt;

&lt;p&gt;The architecture is a feedback loop: domain scoping controls what enters; lifecycle states control what gets queried; retrieval grounds generation; citation audit classifies the output; the contradiction resolver closes the loop. When a conflict is found, a human resolves it, the page is corrected, and the knowledge base improves. Errors become visible rather than accumulating silently.&lt;/p&gt;

&lt;p&gt;That's a more defensible position than any number of ALL CAPS instructions.&lt;/p&gt;




&lt;p&gt;Synthadoc is open-source. The citation faithfulness audit, contradiction resolver, and lifecycle system described here are all in the follwoing repository.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://github.com/axoviq-ai/synthadoc" rel="noopener noreferrer"&gt;github.com/axoviq-ai/synthadoc&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>rag</category>
      <category>wiki</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Paul Chen</dc:creator>
      <pubDate>Tue, 11 Aug 2026 14:08:59 +0000</pubDate>
      <link>https://dev.to/paul_chen_90371fe7426cb44/-53lp</link>
      <guid>https://dev.to/paul_chen_90371fe7426cb44/-53lp</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/paul_chen_90371fe7426cb44/from-manual-commands-to-intent-driven-maintenance-the-agentic-workflow-2d27" class="crayons-story__hidden-navigation-link"&gt;From Manual Commands to Intent-Driven Maintenance: The Agentic Workflow&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/paul_chen_90371fe7426cb44" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3898190%2F38f30ba0-280f-4a52-8c7d-c773315b8da8.jpg" alt="paul_chen_90371fe7426cb44 profile" class="crayons-avatar__image" width="96" height="96"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/paul_chen_90371fe7426cb44" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Paul Chen
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Paul Chen
                
              
              &lt;div id="story-author-preview-content-4363687" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/paul_chen_90371fe7426cb44" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3898190%2F38f30ba0-280f-4a52-8c7d-c773315b8da8.jpg" class="crayons-avatar__image" alt="" width="96" height="96"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Paul Chen&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/paul_chen_90371fe7426cb44/from-manual-commands-to-intent-driven-maintenance-the-agentic-workflow-2d27" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Aug 10&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/paul_chen_90371fe7426cb44/from-manual-commands-to-intent-driven-maintenance-the-agentic-workflow-2d27" id="article-link-4363687"&gt;
          From Manual Commands to Intent-Driven Maintenance: The Agentic Workflow
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/agents"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;agents&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/llm"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;llm&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/automation"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;automation&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/paul_chen_90371fe7426cb44/from-manual-commands-to-intent-driven-maintenance-the-agentic-workflow-2d27" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;5&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/paul_chen_90371fe7426cb44/from-manual-commands-to-intent-driven-maintenance-the-agentic-workflow-2d27#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>From Manual Commands to Intent-Driven Maintenance: The Agentic Workflow</title>
      <dc:creator>Paul Chen</dc:creator>
      <pubDate>Mon, 10 Aug 2026 22:15:47 +0000</pubDate>
      <link>https://dev.to/paul_chen_90371fe7426cb44/from-manual-commands-to-intent-driven-maintenance-the-agentic-workflow-2d27</link>
      <guid>https://dev.to/paul_chen_90371fe7426cb44/from-manual-commands-to-intent-driven-maintenance-the-agentic-workflow-2d27</guid>
      <description>&lt;p&gt;Here's a maintenance loop that every wiki eventually produces.&lt;/p&gt;

&lt;p&gt;A source document gets updated. The wiki page derived from it goes stale. You run &lt;code&gt;synthadoc ingest&lt;/code&gt; to reprocess the file. You wait. You run lint to check if the page passes quality checks. You wait again. If it passes, lint promotes it to active. If it doesn't, you diagnose the issue and repeat. Now multiply that by twelve stale pages.&lt;/p&gt;

&lt;p&gt;It's a well-defined sequence. It's also tedious — each step blocks on the previous one, you have to stay present for the whole thing, and the surface area for forgetting a step is real.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/axoviq-ai/synthadoc" rel="noopener noreferrer"&gt;Synthadoc v1.2&lt;/a&gt; ships an agentic maintenance workflow that replaces this loop with a single prompt.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the agent actually does
&lt;/h2&gt;

&lt;p&gt;When you send "re-ingest stale pages," the &lt;code&gt;ActionAgent&lt;/code&gt; doesn't just parse your intent and hand it to a fixed function. It runs a tool-call loop: the model emits a structured tool call, the agent executes it, returns the result to the model, and the model decides what to call next. This continues until the model produces a plain-text summary with no pending tool calls.&lt;/p&gt;

&lt;p&gt;For the stale-page workflow, the sequence looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;find_stale_pages&lt;/code&gt;&lt;/strong&gt; : returns every stale page with its source file path&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;confirm&lt;/code&gt;&lt;/strong&gt; : sends a confirmation request to the UI listing the scope; blocks until you approve or decline&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ingest_source&lt;/code&gt;&lt;/strong&gt; (×N) : force-reingests one source at a time, polling until each job reaches a terminal state&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;run_lint&lt;/code&gt;&lt;/strong&gt; : enqueues a full lint pass&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;poll_job&lt;/code&gt;&lt;/strong&gt; : waits for the lint job to complete&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;get_page_states&lt;/code&gt;&lt;/strong&gt; : checks the final lifecycle state of every reingested page&lt;/li&gt;
&lt;li&gt;Plain-text summary with every outcome, lint result, and page states&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The model drives all of it. If one ingest fails mid-workflow, the agent continues with the remaining pages and reports the failure in the summary. No hardcoded branching, the failure handling comes from the system prompt the model operates under.&lt;/p&gt;




&lt;h2&gt;
  
  
  The confirmation gate
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;confirm&lt;/code&gt; tool is not a courtesy. Before the agent writes anything, it sends a &lt;code&gt;confirm_request&lt;/code&gt; event to the UI carrying the full scope — which pages, which source files — and blocks until you respond. The 120-second timeout defaults to declined rather than approved.&lt;/p&gt;

&lt;p&gt;This matters because "re-ingest stale pages" and "re-ingest all pages, including ones I deliberately left stale" look identical to a system without a confirmation step. The gate keeps you in control of what gets rewritten.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real-time progress
&lt;/h2&gt;

&lt;p&gt;A workflow that takes three minutes and shows output only at the end feels like it hung. The agentic workflow emits &lt;code&gt;tool_progress&lt;/code&gt; SSE events at each tool step — "Re-ingesting: alan-turing.md", "Ingest running... (7s)", "✓ alan-turing re-ingested", "Running lint check..." — so the UI stays live throughout.&lt;/p&gt;

&lt;p&gt;The same events surface in the Obsidian plugin's query modal. After the workflow completes, the web UI pre-fills the chat textarea with the logical next action — "Run lint to promote re-ingested pages" — so the next step is one click, not a typed command.&lt;/p&gt;




&lt;h2&gt;
  
  
  Works from anywhere
&lt;/h2&gt;

&lt;p&gt;The same agentic loop runs regardless of where you initiate it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Web UI&lt;/strong&gt;: type the prompt in the chat interface, watch progress stream in. Or from the knowledge graph, right-click any stale page node and trigger a per-slug reingest directly from the graph view&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CLI&lt;/strong&gt;: &lt;code&gt;synthadoc query "re-ingest stale pages"&lt;/code&gt; runs the same agent, same confirmation prompt, same result&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The CLI path surfaces &lt;code&gt;tool_progress&lt;/code&gt; events as terminal lines and handles the confirm gate interactively with a &lt;code&gt;[Yes/Cancel]&lt;/code&gt; prompt, so the workflow is fully usable without a browser.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pluggable by design
&lt;/h2&gt;

&lt;p&gt;The workflow is built on an abstract interface: &lt;code&gt;AgenticWorkflow&lt;/code&gt; declares &lt;code&gt;build_system_prompt()&lt;/code&gt;, &lt;code&gt;build_initial_message()&lt;/code&gt;, and &lt;code&gt;get_tool_fns()&lt;/code&gt;. The loop machinery — tool dispatch, result injection, termination detection, the 30-call limit, the confirmation timeout — lives in the action agent and is inherited by every workflow.&lt;/p&gt;

&lt;p&gt;Adding a new workflow means implementing three methods and writing a system prompt. The existing infrastructure handles the rest.&lt;/p&gt;




&lt;h2&gt;
  
  
  See it running
&lt;/h2&gt;

&lt;p&gt;The walkthrough below covers the full loop end to end: two stale pages, the confirmation gate, per-page ingest progress, lint auto-run, and the content snapshot diff in Obsidian showing exactly what changed between versions.&lt;/p&gt;

&lt;p&gt;📺 &lt;strong&gt;&lt;a href="https://www.youtube.com/watch?v=ojBtNlXVHQk" rel="noopener noreferrer"&gt;Synthadoc Agentic Maintenance Workflow — YouTube&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What we learned building this
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;System prompts are the policy layer.&lt;/strong&gt; The rules governing the workflow — discover scope first, always confirm before writing, process pages one at a time, run lint after all ingests — live in the system prompt, not in code. When we needed to adjust the ordering for the per-slug path, it was four lines of text, not a code change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-step progress makes everything feel faster.&lt;/strong&gt; The actual duration doesn't change. The perceived latency does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One failure shouldn't stop the batch.&lt;/strong&gt; If a source file disappears between discovery and ingest, the workflow reports the failure and keeps going. Wiring that in from the start saved a real debugging session later.&lt;/p&gt;




&lt;p&gt;Synthadoc CE v1.2.0 is open source. The agentic workflow, the pluggable interface, and the full SSE protocol are all in the repo.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>llm</category>
      <category>automation</category>
    </item>
    <item>
      <title>Which LLM Gives You the Best Accuracy-to-Cost Ratio for Knowledge Base Queries?</title>
      <dc:creator>Paul Chen</dc:creator>
      <pubDate>Fri, 10 Jul 2026 18:47:51 +0000</pubDate>
      <link>https://dev.to/paul_chen_90371fe7426cb44/which-llm-gives-you-the-best-accuracy-to-cost-ratio-for-knowledge-base-queries-1e3d</link>
      <guid>https://dev.to/paul_chen_90371fe7426cb44/which-llm-gives-you-the-best-accuracy-to-cost-ratio-for-knowledge-base-queries-1e3d</guid>
      <description>&lt;p&gt;When you build a knowledge base powered by an LLM, you eventually have to answer a practical question: &lt;em&gt;which model should the query agent actually use?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This post shares our findings from a structured evaluation across five models. The short version: the cheapest viable option also won on accuracy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/axoviq-ai/synthadoc" rel="noopener noreferrer"&gt;Synthadoc&lt;/a&gt; is an open-source LLM wiki compilation engine. It reads your raw source documents — PDFs, spreadsheets, web pages, Word files — and synthesizes them into a structured, cross-referenced wiki at ingest time. Knowledge is compiled once, not re-summarized on every query.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;QueryAgent&lt;/strong&gt; sits at the front of every question. It decomposes the query, runs parallel BM25 retrieval across the compiled wiki, assembles a proportional context window, and calls the configured LLM to synthesize an answer with inline citations. It's the most token-intensive path in the system, and the one where model choice has the most visible impact on both quality and cost.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why We Ran This Evaluation
&lt;/h2&gt;

&lt;p&gt;Synthadoc supports a wide range of LLM providers: Anthropic, MiniMax, DeepSeek, Qwen, Gemini, Groq, and local Ollama. Choosing between them used to be a matter of intuition. We wanted data.&lt;/p&gt;

&lt;p&gt;The specific question: &lt;strong&gt;is a more expensive model meaningfully more accurate, or is there a sweet spot that gives you most of the quality at a fraction of the cost?&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Evaluation Setup
&lt;/h2&gt;

&lt;p&gt;We used the &lt;strong&gt;AquaFlow Capital&lt;/strong&gt; scenario, a private equity M&amp;amp;A due diligence wiki built from eight deal documents (LBO model mechanics, quality of earnings, covenant framework, ESG standards, legal DD process, and exit valuation benchmarks).&lt;/p&gt;

&lt;p&gt;We ran &lt;strong&gt;15 structured questions&lt;/strong&gt; across three complexity tiers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;English - Medium&lt;/strong&gt; (Q1–Q5): factual retrieval, single-page lookups&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;English - High&lt;/strong&gt; (Q6–Q10): multi-source synthesis, numerical reasoning&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-lingual Chinese&lt;/strong&gt; (Q11–Q15): same questions in Chinese, requiring CJK retrieval&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Five models were evaluated: &lt;strong&gt;MiniMax-Think (M3)&lt;/strong&gt;, &lt;strong&gt;Claude Opus 4.8&lt;/strong&gt;, &lt;strong&gt;Claude Sonnet 4.6&lt;/strong&gt;, &lt;strong&gt;DeepSeek-R1 (V3)&lt;/strong&gt;, and &lt;strong&gt;Qwen Plus&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Each answer was graded on a 0–100 scale against a ground-truth rubric. PASS threshold: 85%.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We Found
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxvqwacbbyjf75coxaf89.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxvqwacbbyjf75coxaf89.png" alt="Accuracy vs. Cost per 1K Queries" width="800" height="637"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The scatter tells the story clearly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MiniMax-Think (M3) sits alone in the top-left&lt;/strong&gt;, highest accuracy at the lowest cost. That's the ideal quadrant, and no other model comes close to occupying it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Accuracy&lt;/th&gt;
&lt;th&gt;Est. cost / 1K queries&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MiniMax-Think (M3)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;92%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~$25–40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Opus 4.8&lt;/td&gt;
&lt;td&gt;89%&lt;/td&gt;
&lt;td&gt;~$1,050&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Sonnet 4.6&lt;/td&gt;
&lt;td&gt;86%&lt;/td&gt;
&lt;td&gt;~$210&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek-R1 (V3)&lt;/td&gt;
&lt;td&gt;78%&lt;/td&gt;
&lt;td&gt;~$35–55&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen Plus&lt;/td&gt;
&lt;td&gt;73%&lt;/td&gt;
&lt;td&gt;~$30–50&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Claude Opus 4.8 scored 89%, just 3 percentage points below MiniMax-Think, but at roughly &lt;strong&gt;5× the cost of Sonnet and 26× the cost of MiniMax&lt;/strong&gt;. For a query workload measured in thousands of questions, that gap is significant.&lt;/p&gt;

&lt;p&gt;Claude Sonnet 4.6 at 86% is the best option if you're locked to the Anthropic Claude or its API and need reliable citation quality.&lt;/p&gt;

&lt;p&gt;DeepSeek-R1 at ~$40/1K queries is a solid budget pick for exploratory or conceptual synthesis queries, where synthesis depth matters more than precision.&lt;/p&gt;




&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;For the AquaFlow domain - dense financial documents, multilingual queries, numerical reasoning - &lt;strong&gt;MiniMax-Think (M3) is the clear default.&lt;/strong&gt; It wins on accuracy &lt;em&gt;and&lt;/em&gt; cost simultaneously. That's a rare combination.&lt;/p&gt;

&lt;p&gt;Model choice isn't just a quality decision. At scale, it's a cost architecture decision.&lt;/p&gt;




&lt;p&gt;The full report includes per-question breakdowns, cost math, WARN pattern analysis (where each model fails and why), and a per-question line chart across all 15 questions:&lt;/p&gt;

&lt;p&gt;👉&lt;strong&gt;&lt;a href="https://github.com/axoviq-ai/synthadoc/blob/main/docs/example/aquaflow/evaluation/report/llm-query-benchmark.md" rel="noopener noreferrer"&gt;Full evaluation report on GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>machinelearning</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Interfaces Layer Design Pattern for AI Knowledgebase</title>
      <dc:creator>Paul Chen</dc:creator>
      <pubDate>Tue, 23 Jun 2026 16:01:30 +0000</pubDate>
      <link>https://dev.to/paul_chen_90371fe7426cb44/four-interfaces-layer-design-pattern-for-ai-knowledge-systems-4cbh</link>
      <guid>https://dev.to/paul_chen_90371fe7426cb44/four-interfaces-layer-design-pattern-for-ai-knowledge-systems-4cbh</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkqj3wggck9zne8yi1lwa.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkqj3wggck9zne8yi1lwa.jpg" alt="Four Interfaces: CLI, Obsidian, Web UI &amp;amp; MCP" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;▶ &lt;a href="https://youtu.be/ue_kHhG0iog" rel="noopener noreferrer"&gt;Watch on YouTube&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most AI knowledge tools make a quiet assumption: the model is the worker, and the human is the caller.&lt;/p&gt;

&lt;p&gt;You ask, the system retrieves pages, the LLM synthesises them, you get an answer. Clean pipeline. One direction.&lt;/p&gt;

&lt;p&gt;That assumption breaks the moment a reasoning model needs to &lt;em&gt;use&lt;/em&gt; the knowledge base as a tool - not to answer a question, but to do work. Search for related pages. Read a contradicted entry. Write a resolution. Commit a lifecycle transition. Then search again based on what it found.&lt;/p&gt;

&lt;p&gt;Now the model is the caller. The knowledge layer is the called. Same storage. Opposite direction.&lt;/p&gt;

&lt;p&gt;This is the coordination problem that most knowledge tools never have to solve, because they pick one mode and stay there. Building a system that handles both cleanly turns out to be the interesting design challenge.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Four-Interface Pattern
&lt;/h2&gt;

&lt;p&gt;The clearest way to see the tension is through the four surfaces a well-designed knowledge system needs to support:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CLI&lt;/strong&gt; : a human (or script) calls the system, which calls an LLM, which returns an answer. Streaming for humans, structured output for pipes. The model is a service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IDE/App Plugin&lt;/strong&gt; : same direction, richer context. The plugin renders wikilinks as navigation, exposes a lifecycle dashboard for human decisions. Still: human calls system calls LLM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Web Chat UI&lt;/strong&gt; : conversational, multi-turn, natural language operations. "Run lint." "Show me stale pages." The system interprets these and dispatches them. Still: human calls system calls LLM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP&lt;/strong&gt; : a reasoning model connects to the knowledge layer as a set of tools. It decides what to search, what to read, when to write, which lifecycle state to commit. The system is now &lt;em&gt;memory&lt;/em&gt;. The model is now the &lt;em&gt;brain&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The insight is that the first three interfaces share a common structure. The fourth inverts it entirely.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why the Memory Layer Needs Opinions
&lt;/h2&gt;

&lt;p&gt;Here is where the design gets non-obvious.&lt;/p&gt;

&lt;p&gt;When a reasoning model can freely read, write, and transition pages, you need the storage layer to enforce constraints the model might not. A lifecycle transition graph that blocks invalid state changes. An immutable audit trail that records every edit and its stated reason, regardless of whether a human or an agent made it. Provenance tracking that ties every page to its source material.&lt;/p&gt;

&lt;p&gt;These aren't features for the human users. They're the constraints that make it safe to connect a reasoning model to the same base a human is actively maintaining.&lt;/p&gt;

&lt;p&gt;A model can call &lt;code&gt;write_page&lt;/code&gt; then &lt;code&gt;lifecycle_transition&lt;/code&gt;. What it cannot do is move a page from &lt;code&gt;stale&lt;/code&gt; to &lt;code&gt;contradicted&lt;/code&gt; - that transition isn't in the allowed graph. The memory layer enforces this the same way it enforces it for a CLI command. It doesn't care which surface issued the instruction.&lt;/p&gt;

&lt;p&gt;The audit trail records &lt;code&gt;triggered_by: mcp&lt;/code&gt; and the model's stated reason. Run the lifecycle log the next morning and the full chain is there, whether it was a human clicking in a plugin or an agent running at 2 AM.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Hint Engine Doesn't Disappear
&lt;/h2&gt;

&lt;p&gt;One question that comes up: if a reasoning model generates its own next steps dynamically, what happens to the deterministic hint engine that suggests follow-up queries?&lt;/p&gt;

&lt;p&gt;It serves the human-facing interfaces. No LLM cost, instant suggestions based on current wiki health, contextual to what the user just asked. A reasoning model doesn't need that, it generates its own chains based on what it finds. Two different users, two different workflows, same storage layer.&lt;/p&gt;

&lt;p&gt;The coexistence is the point. The four-interface pattern isn't about choosing between human-driven and agent-driven workflows. It's about a knowledge layer that supports both without leaking the distinction into the storage model.&lt;/p&gt;




&lt;p&gt;Reference Implementation&lt;/p&gt;

&lt;p&gt;The pattern described here is implemented in &lt;a href="https://github.com/axoviq-ai/synthadoc" rel="noopener noreferrer"&gt;Synthadoc&lt;/a&gt;, an open-source AI knowledge base tool. The video above walks through all four interfaces against a live demo wiki - CLI queries, Obsidian plugin lifecycle management, web chat multi-turn conversation, and connecting Claude Code as a reasoning brain over MCP.&lt;/p&gt;

&lt;p&gt;If you're building something similar, the part worth paying attention to isn't the interface count, it's the audit trail and transition graph. Those are what make the memory layer safe to connect to a model that can write, not just read.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>mcp</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Google's OKF Is Proposing a Standard for How AI Agents Consume Domain Knowledge - Here's How Synthadoc Already Fits</title>
      <dc:creator>Paul Chen</dc:creator>
      <pubDate>Mon, 15 Jun 2026 14:03:53 +0000</pubDate>
      <link>https://dev.to/paul_chen_90371fe7426cb44/googles-okf-is-proposing-a-standard-for-how-ai-agents-consume-domain-knowledge-heres-how-3nh9</link>
      <guid>https://dev.to/paul_chen_90371fe7426cb44/googles-okf-is-proposing-a-standard-for-how-ai-agents-consume-domain-knowledge-heres-how-3nh9</guid>
      <description>&lt;p&gt;On June 12, 2026, Google Cloud published &lt;strong&gt;&lt;a href="https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md" rel="noopener noreferrer"&gt;Open Knowledge Format (OKF) v0.1&lt;/a&gt;&lt;/strong&gt; - a vendor-neutral specification for how AI agents should consume structured knowledge. If you've been thinking about how to give your agents reliable, domain-specific memory beyond RAG over raw documents, this is worth your attention.&lt;/p&gt;

&lt;p&gt;We built Synthadoc to solve exactly this problem, and the OKF spec reads like a formal description of what we built. Independent convergence on the same design is usually a sign that both teams found something real.&lt;/p&gt;

&lt;p&gt;This post breaks down what OKF is, where Synthadoc already aligns with it, how we're converging on the remaining gaps, and why we think Synthadoc is one of the most important implementations the OKF ecosystem needs.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Problem OKF Is Solving
&lt;/h2&gt;

&lt;p&gt;Organizations today fragment their knowledge across metadata catalogs, wikis, Notion pages, code comments, and internal documents. When you want an AI agent to reason over that knowledge, you face a choice: either build a custom pipeline for each source, or dump everything into a vector store and hope similarity search finds the right facts.&lt;/p&gt;

&lt;p&gt;OKF proposes a third path: &lt;strong&gt;a portable, file-based format for curated knowledge that any AI agent can consume without vendor-specific APIs or custom parsers.&lt;/strong&gt; The format is deliberately minimal - markdown files with YAML frontmatter, organized in directories whose paths represent concept identity. Standard markdown links between files create a traversable knowledge graph.&lt;/p&gt;

&lt;p&gt;The only required field is &lt;code&gt;type&lt;/code&gt;. Everything else - &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;tags&lt;/code&gt;, &lt;code&gt;timestamp&lt;/code&gt;, &lt;code&gt;resource&lt;/code&gt; - is optional. The philosophy is producer/consumer independence: the same format works whether you generated it from a data catalog, a wiki, or a code annotation pipeline.&lt;/p&gt;

&lt;p&gt;This is a strong design philosophy. It means OKF bundles are readable by humans in any text editor, versionable in git, and consumable by any agent that can read files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Google's reference implementation
&lt;/h3&gt;

&lt;p&gt;Alongside the spec, Google shipped three things: an &lt;strong&gt;Enrichment Agent&lt;/strong&gt; that ingests BigQuery metadata and emits OKF bundles (built on Google's Agent Development Kit with Gemini as the backend), a &lt;strong&gt;Visualizer&lt;/strong&gt; that renders any OKF bundle as a self-contained interactive HTML file using Cytoscape.js, and three &lt;strong&gt;pre-built sample bundles&lt;/strong&gt; for GA4 e-commerce, Stack Overflow, and Bitcoin datasets.&lt;/p&gt;

&lt;p&gt;The Enrichment Agent is worth studying. It is a working OKF producer, a concrete proof that the format is implementable, not just specifiable. But its source material is structured: BigQuery tables, schemas, foreign-key relationships, data catalog metadata. This is one important slice of the OKF producer space.&lt;/p&gt;

&lt;p&gt;The other slice - the harder one for most organisations - is unstructured source material: internal PDFs, research papers, spreadsheets, meeting notes, product documentation. That is the gap Synthadoc fills. Google's Enrichment Agent and Synthadoc are complementary producers in the same ecosystem, covering different source types with the same output format.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Google Enrichment Agent&lt;/th&gt;
&lt;th&gt;Synthadoc&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Source material&lt;/td&gt;
&lt;td&gt;BigQuery datasets, data catalogs&lt;/td&gt;
&lt;td&gt;PDFs, XLSX, PNG, plain text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OKF role&lt;/td&gt;
&lt;td&gt;Producer&lt;/td&gt;
&lt;td&gt;Producer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lifecycle management&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;5-state (draft → archived)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conflict resolution&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Contradicted state + auto-resolve&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Human editorial gate&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;draft → active transition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM backend&lt;/td&gt;
&lt;td&gt;Gemini only&lt;/td&gt;
&lt;td&gt;Gemini, Anthropic, OpenAI, Groq, Qwen, Ollama, Deepseek, Minimax&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Synthadoc's Design Was Already There
&lt;/h2&gt;

&lt;p&gt;Synthadoc compiles raw documents (PDFs, spreadsheets, images, Youtube video, plain text) into a structured wiki - a collection of markdown files in a directory, each with YAML frontmatter, linked together by wikilinks. That wiki is then queryable by both humans (through Obsidian or a web UI) and AI agents (through the API).&lt;/p&gt;

&lt;p&gt;Here's what a Synthadoc wiki page looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Alan Turing&lt;/span&gt;
&lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;biography&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;theory&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;cryptography&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;active&lt;/span&gt;
&lt;span class="na"&gt;confidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;high&lt;/span&gt;
&lt;span class="na"&gt;created&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-04-08&lt;/span&gt;
&lt;span class="na"&gt;sources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;turing-biography.pdf&lt;/span&gt;
    &lt;span class="na"&gt;hash&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sha256:9f3a...&lt;/span&gt;
    &lt;span class="na"&gt;ingested&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-04-08&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="c1"&gt;# Alan Turing&lt;/span&gt;

&lt;span class="s"&gt;Alan Mathison Turing (1912–1954) was an English mathematician and computer scientist...&lt;/span&gt;

&lt;span class="s"&gt;The [[von-neumann-architecture]] that underlies modern computers shares conceptual roots&lt;/span&gt;
&lt;span class="s"&gt;with Turing's stored-program ideas. Turing also influenced [[programming-languages-overview]]&lt;/span&gt;
&lt;span class="s"&gt;by formalising what it means for a problem to be computable.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we mapped this against the OKF specification, the alignment was immediate:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;OKF requirement&lt;/th&gt;
&lt;th&gt;Synthadoc&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Markdown + YAML frontmatter&lt;/td&gt;
&lt;td&gt;Native format for all wiki pages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;index.md&lt;/code&gt; reserved for navigation&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;wiki/index.md&lt;/code&gt; in every wiki — auto-generated and maintained&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hierarchical concept organization&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ROUTING.md&lt;/code&gt; groups pages into topic branches (&lt;code&gt;People&lt;/code&gt;, &lt;code&gt;Hardware&lt;/code&gt;, etc.) -&amp;nbsp;logical hierarchy via manifest rather than physical directory structure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Markdown links as knowledge graph edges&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;[[wikilinks]]&lt;/code&gt; compile to a traversable knowledge graph&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;tags&lt;/code&gt; field&lt;/td&gt;
&lt;td&gt;First-class frontmatter field on every page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;timestamp&lt;/code&gt; (ISO 8601)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;created&lt;/code&gt; and lifecycle timestamps throughout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No proprietary SDK required&lt;/td&gt;
&lt;td&gt;Standard files, readable by any tool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Git-compatible&lt;/td&gt;
&lt;td&gt;Every wiki is a git-managed directory&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The core insight OKF encodes that knowledge for agents should live in plain files with structured metadata, not locked in a database, is the same insight Synthadoc was built on.&lt;/p&gt;




&lt;h2&gt;
  
  
  Converging with OKF
&lt;/h2&gt;

&lt;p&gt;Synthadoc was designed independently before OKF was published, so a few intentional design choices don't yet match the spec exactly. Here's what they are and how we're closing each gap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wikilinks → standard markdown links.&lt;/strong&gt;&lt;br&gt;
OKF uses &lt;code&gt;[Alan Turing](people/alan-turing.md)&lt;/code&gt;; Synthadoc uses &lt;code&gt;[[alan-turing]]&lt;/code&gt;. The graph structure is identical - only the syntax differs. We're adding an OKF export mode that rewrites wikilinks to standard markdown links at export time, so any Synthadoc wiki can be published as a fully spec-compliant OKF bundle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adding the &lt;code&gt;type&lt;/code&gt; field.&lt;/strong&gt;&lt;br&gt;
OKF's only required field is &lt;code&gt;type&lt;/code&gt; , a classifier for what kind of knowledge the page contains (&lt;code&gt;concept&lt;/code&gt;, &lt;code&gt;person&lt;/code&gt;, &lt;code&gt;technology&lt;/code&gt;, &lt;code&gt;event&lt;/code&gt;). Synthadoc's frontmatter uses &lt;code&gt;status&lt;/code&gt; for lifecycle state and &lt;code&gt;confidence&lt;/code&gt; for epistemic weight - complementary metadata, not a replacement. We're adding &lt;code&gt;type&lt;/code&gt; to the schema in the next release. With that one field, every Synthadoc wiki page will be OKF-compliant out of the box.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;sources&lt;/code&gt; → &lt;code&gt;resource&lt;/code&gt; alignment.&lt;/strong&gt;&lt;br&gt;
OKF's &lt;code&gt;resource&lt;/code&gt; field is a single URL pointing to an external source. Synthadoc's &lt;code&gt;sources&lt;/code&gt; array goes deeper: each entry includes the file path, SHA-256 hash, file size, and ingestion date, and individual claims carry line-level citations. This is a superset of what OKF specifies, not a conflict. The OKF export will surface the primary source URL in the &lt;code&gt;resource&lt;/code&gt; field while preserving Synthadoc's full provenance in the body.&lt;/p&gt;

&lt;p&gt;These three changes give Synthadoc full OKF compliance while keeping the richer metadata that makes the wiki useful for humans and auditors, not just agents.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where Synthadoc Goes Further
&lt;/h2&gt;

&lt;p&gt;OKF v0.1 explicitly lists several open design questions - problems the specification hasn't solved yet. These are interesting to us because they're exactly what Synthadoc was designed to handle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contradiction resolution.&lt;/strong&gt; When two source documents say conflicting things about the same topic, OKF has no answer yet. Synthadoc flags the affected page as &lt;code&gt;contradicted&lt;/code&gt; and can automatically propose a reconciled view using LLM-assisted lint. The resolution is recorded in the page's lifecycle audit trail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stale information management.&lt;/strong&gt; OKF acknowledges this as an open problem. Synthadoc has a &lt;code&gt;stale&lt;/code&gt; lifecycle state triggered when a source document changes after initial ingest. Scheduled lint runs detect drift and surface pages that need review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adversarial review.&lt;/strong&gt; OKF doesn't address how to audit knowledge for unsupported claims or overconfident assertions. Synthadoc's lint pass runs an adversarial review on each page, flagging claims that lack source support and writing findings directly into &lt;code&gt;lint_warnings&lt;/code&gt; frontmatter.&lt;/p&gt;

&lt;p&gt;These aren't listed as criticisms of OKF v0.1 is explicitly a "starting point for community feedback." They're listed because they represent the class of problems any serious knowledge management system needs to solve as it matures.&lt;/p&gt;




&lt;h2&gt;
  
  
  Synthadoc as an OKF Producer: Bridging Agents to Domain Knowledge
&lt;/h2&gt;

&lt;p&gt;Here's the framing we find most compelling.&lt;/p&gt;

&lt;p&gt;OKF describes a &lt;em&gt;consumption format&lt;/em&gt; , a way for AI agents to read structured knowledge. What it doesn't describe is how that knowledge gets created, validated, kept current, and made queryable. That's the production problem.&lt;/p&gt;

&lt;p&gt;Synthadoc solves the production problem. It ingests raw documents from your organization, compiles them into a structured wiki, validates each page through adversarial lint, manages the page lifecycle as sources change over time, and serves the result through a query API.&lt;/p&gt;

&lt;p&gt;The output of Synthadoc, a directory of markdown files with YAML frontmatter, linked by wikilinks, with an &lt;code&gt;index.md&lt;/code&gt;, is naturally aligned with what OKF describes as agent-consumable knowledge.&lt;/p&gt;

&lt;p&gt;This means you can think of Synthadoc wikis as &lt;strong&gt;OKF-compatible knowledge bundles&lt;/strong&gt; that cover the full lifecycle:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1vfxok319ui3ja426w3l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1vfxok319ui3ja426w3l.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're building an agent that needs domain expertise, a customer support agent that needs product knowledge, a research agent that needs a scientific literature summary - a coding assistant that needs your internal architecture decisions - Synthadoc turns your existing documents into a structured, validated, self-maintaining knowledge base the agent can trust.&lt;/p&gt;




&lt;h2&gt;
  
  
  Our Perspective: What the Standard Still Needs
&lt;/h2&gt;

&lt;p&gt;OKF v0.1 is a strong starting point, and we think the direction is right. But having built in this space, we have a few opinions worth putting on record, not as criticism, but as a contribution to where v0.2 should go.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A format standard without a quality standard is garbage-in, garbage-out.&lt;/strong&gt;&lt;br&gt;
If any tool can produce an OKF bundle, agents will inevitably consume contradictory, stale, or unsupported content formatted as valid OKF. The spec defines how knowledge is structured, but not how it is validated. The ecosystem needs production tooling that enforces quality - adversarial review, conflict detection, lifecycle management - before knowledge reaches an agent. A format alone doesn't make a knowledge base trustworthy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent memory needs trust signals, not just content.&lt;/strong&gt;&lt;br&gt;
OKF sepc currently gives an agent no way to know how confident to be in what it's reading. A page written two years ago from a single unverified source should carry different weight than one reviewed last week, cross-referenced against multiple documents, and cleared by an adversarial lint pass. &lt;code&gt;confidence&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;, and claim-level provenance need to become first-class concepts in the spec, not optional extensions, if agents are to make consequential decisions from OKF knowledge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Domain-specific knowledge is where this matters most.&lt;/strong&gt;&lt;br&gt;
General facts are already baked into LLM weights. What agents actually need is proprietary, time-sensitive, domain-specific knowledge that cannot be in training data - internal architecture decisions, evolving product specifications, specialist research. This is precisely where OKF is most valuable, and also where freshness and accuracy matter most. A standard that doesn't address knowledge currency will struggle in exactly the use case it's most suited for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The human editorial layer is non-negotiable for consequential decisions.&lt;/strong&gt;&lt;br&gt;
Fully automated pipelines - ingest everything, trust everything - break down when agents act on the results. Knowledge intended for agent consumption needs a defined editorial state: has a human reviewed this page and cleared it for use? Synthadoc's &lt;code&gt;draft → active&lt;/code&gt; lifecycle transition is exactly this gate. We think OKF should define a concept of editorial provenance so consumers can distinguish machine-generated drafts from human-validated knowledge.&lt;/p&gt;




&lt;h2&gt;
  
  
  Synthadoc v0.8.0
&lt;/h2&gt;

&lt;p&gt;The OKF alignment discussed in this post ships alongside &lt;strong&gt;&lt;a href="https://github.com/axoviq-ai/synthadoc/releases/tag/v0.8.0" rel="noopener noreferrer"&gt;Synthadoc v0.8.0&lt;/a&gt;&lt;/strong&gt;, released June 12, the same day as OKF v0.1. The release focuses on three areas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multi-turn conversation&lt;/strong&gt; : the web UI maintains full session history across turns. Follow-up questions resolve correctly through context injection and automatic phrase rewriting. Long sessions compress older turns into summaries rather than discarding them. Sessions persist server-side and survive page refreshes; the sidebar shows history as a collapsible tree with turn-count badges.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Qwen DashScope + Claude Opus 4.8&lt;/strong&gt; : Qwen cloud models (&lt;code&gt;qwen-plus&lt;/code&gt;, &lt;code&gt;qwen-max&lt;/code&gt;, &lt;code&gt;qwq-32b&lt;/code&gt;) now work via DashScope's OpenAI-compatible endpoint. New DashScope accounts receive 1 million free tokens valid for 90 days. Claude Opus 4.8 is supported for maximum answer quality.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conversational job operations&lt;/strong&gt; : the Action Agent handles live job queries directly in chat. Ask for job status and receive a real-time table; job IDs appear as clickable chips for drill-down. Multi-status filters ("show failed and skipped jobs") work across turns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you find Synthadoc useful, a ⭐ on GitHub helps the project reach more people: &lt;a href="https://github.com/axoviq-ai/synthadoc" rel="noopener noreferrer"&gt;https://github.com/axoviq-ai/synthadoc&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md" rel="noopener noreferrer"&gt;OKF v0.1 Specification&lt;/a&gt; : GoogleCloudPlatform/knowledge-catalog&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/axoviq-ai/synthadoc" rel="noopener noreferrer"&gt;Synthadoc&lt;/a&gt; : github.com/axoviq-ai/synthadoc&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>automation</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Synthadoc: A Self-Invalidating Query Cache</title>
      <dc:creator>Paul Chen</dc:creator>
      <pubDate>Thu, 11 Jun 2026 02:12:28 +0000</pubDate>
      <link>https://dev.to/paul_chen_90371fe7426cb44/synthadoc-a-self-invalidating-query-cache-4d01</link>
      <guid>https://dev.to/paul_chen_90371fe7426cb44/synthadoc-a-self-invalidating-query-cache-4d01</guid>
      <description>&lt;p&gt;Most LLM queries feel expensive the second time. You've already asked your wiki "What is Moore's Law?" this morning. The answer hasn't changed - nothing in your wiki has changed - but if you ask again this afternoon, Synthadoc will hit the retrieval pipeline, build a prompt, call the LLM, and wait 2–10 seconds for an answer you already have.&lt;/p&gt;

&lt;p&gt;v0.7.0 eliminates that. A query cache stores the result the first time, and serves it instantly on every repeat - with one rule: the cache invalidates itself automatically whenever your wiki changes. No manual flush, no TTL to configure. You never serve a stale answer, and you never pay twice for the same question.&lt;/p&gt;

&lt;p&gt;This post covers the cache design and the performance benchmarks we ran against it. The streaming query pipeline and web chat UI that shipped alongside it in v0.7.0 are covered in the companion post: &lt;strong&gt;&lt;a href="https://dev.to/paul_chen_90371fe7426cb44/synthadoc-streaming-queries-local-web-chat-and-a-self-invalidating-cache-1hgb"&gt;Synthadoc: Streaming Queries and a Local Web Chat UI&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Query Caching: When Not to Call the LLM
&lt;/h2&gt;

&lt;p&gt;The cache design started from a specific observation: most queries against a domain wiki are repeated. A team maintaining a knowledge base about their software architecture will ask the same questions dozens of times - during onboarding, during incident reviews, during planning sessions. Every one of those calls hits the LLM and incurs both latency and cost.&lt;/p&gt;

&lt;p&gt;The cache eliminates that. But the tricky part is knowing when to invalidate it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache Key Design
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SHA&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;normalized_question&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;wiki_epoch&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;provider_model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three components:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Normalized question&lt;/strong&gt; - lowercased, whitespace-collapsed. "What is Moore's Law?" and "what is moore's law?" hit the same cache entry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wiki epoch&lt;/strong&gt; - an integer counter on the server instance. It starts at 0 on startup and increments on every ingest job completion and every lifecycle state transition. When the epoch changes, the cache key for every question changes. Prior entries don't get deleted immediately, they just become unreachable. Old entries are cleaned up in a background sweep (entries more than 5 epochs behind current, or older than 7 days).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Provider/model&lt;/strong&gt; - &lt;code&gt;"openai/gpt-4o-mini"&lt;/code&gt; or &lt;code&gt;"anthropic/claude-sonnet-4-6"&lt;/code&gt;. Switching models invalidates the cache. A cached answer from a smaller model shouldn't surface when you've upgraded to a better one.&lt;/p&gt;

&lt;p&gt;The epoch approach is what makes invalidation automatic. You don't call "invalidate cache" after an ingest, the epoch bump does it implicitly. Any query after a wiki change computes a new key that has never been seen, misses the cache, and calls the LLM fresh. The previous answer doesn't need to be deleted; it simply ceases to be looked up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Diagram: Cache Lookup, Hit, Miss, and Epoch Invalidation
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmcpzmxgyza7mukezuquy.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F74rs8235uvn3w561lse1.png" alt=" " width="800" height="860"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Measured Results
&lt;/h2&gt;

&lt;p&gt;Rather than leaving the latency claims as estimates, we wrote a full performance test suite against the cache layer. All numbers below come from running &lt;code&gt;pytest tests/performance/test_query_cache_perf.py&lt;/code&gt; locally on a Windows development machine with an SSD. Linux bare-metal numbers are consistently 30–40% better.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chart 1 - Cache read latency distribution (500 reads, 200 cached entries)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuk33uv3b4ftjzpsbe2m3.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi8krbbvmjfc8jn4ztfgy.png" alt="Cache read latency distribution - P50=0.26ms P95=0.34ms P99=0.41ms" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;P50 = &lt;strong&gt;0.26ms&lt;/strong&gt;, P95 = &lt;strong&gt;0.34ms&lt;/strong&gt;, P99 = &lt;strong&gt;0.41ms&lt;/strong&gt; against a 10ms SLO. The distribution is extremely tight - the persistent connection eliminates the per-call connection-open overhead that was the main source of outliers. Every percentile sits well inside the budget with headroom to spare.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chart 2 - Cache hit vs miss latency at varying LLM speeds&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhw4ay8u34xm193ag4a2z.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb60quvichmxffmdta5w3.png" alt="Cache hit vs miss latency and speedup factor at 50ms / 200ms / 500ms / 2000ms simulated LLM" width="800" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The left panel uses a log scale because the gap is so large it can't be shown linearly. Cache hit P50 stays flat at ~0.25ms regardless of LLM speed - one shared persistent connection makes the hit path a pure queue-and-execute SQLite read with no file-open cost. The miss path scales directly with LLM latency. The right panel shows the resulting speedup factor:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Simulated LLM speed&lt;/th&gt;
&lt;th&gt;Cache miss P50&lt;/th&gt;
&lt;th&gt;Cache hit P50&lt;/th&gt;
&lt;th&gt;Speedup&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;50ms (fast provider)&lt;/td&gt;
&lt;td&gt;95ms&lt;/td&gt;
&lt;td&gt;0.29ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~330×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;200ms (mid provider)&lt;/td&gt;
&lt;td&gt;235ms&lt;/td&gt;
&lt;td&gt;0.32ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~730×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;500ms (slow provider)&lt;/td&gt;
&lt;td&gt;544ms&lt;/td&gt;
&lt;td&gt;0.24ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~2270×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2000ms (reasoning model)&lt;/td&gt;
&lt;td&gt;2055ms&lt;/td&gt;
&lt;td&gt;0.26ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~7900×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The cache hit time is so small relative to any real LLM that the ratio is dominated entirely by provider latency. At reasoning models (o3-mini, MiniMax M2) a single saved round-trip reclaims 15–30 seconds of wall time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chart 3 - Concurrent readers: persistent connection scaling curve&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ov6fhi498vrdojk7x8y.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkxjmbwhivwg86t8xwb8i.png" alt="Concurrent cache readers P95 latency vs concurrency - smooth monotonic scaling" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Single reader: &lt;strong&gt;0.5ms P95&lt;/strong&gt;. Ten readers: &lt;strong&gt;2.0ms&lt;/strong&gt;. Twenty-five: &lt;strong&gt;3.8ms&lt;/strong&gt;. Fifty: &lt;strong&gt;7.8ms&lt;/strong&gt;. One hundred: &lt;strong&gt;14.9ms&lt;/strong&gt;. The curve is smooth and monotonically increasing - no Windows spikes, no non-monotonic jitter. All concurrent reads queue through one shared aiosqlite background thread; the connection-open overhead that caused the old instability is simply not there. For a local single-user tool the realistic ceiling is n=5–10 concurrent reads, where P95 is under 2ms. Even at n=100 the tail is well inside a 50ms budget.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chart 4 - Cache vs no-cache throughput (queries/second)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi77o76kkyli94a180vqf.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl2qwr4pckvbaw98zjgjr.png" alt="Cache vs no-cache throughput and advantage ratio at concurrency 1 to 100" width="800" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The throughput advantage starts at &lt;strong&gt;75.7×&lt;/strong&gt; at n=1 and compresses to &lt;strong&gt;4.1×&lt;/strong&gt; at n=100. The compression is expected: &lt;code&gt;asyncio.gather()&lt;/code&gt; parallelises the simulated LLM calls so the no-cache path scales nearly linearly with concurrency. The cache path, sharing one connection, serializes through the aiosqlite queue and grows sublinearly. But critically, the cache always wins by a wide margin - 4.1× at n=100 is far better than the 1.3× seen before the persistent connection fix. At realistic single-user concurrency (n=1–5), the advantage is 33–76×.&lt;/p&gt;




&lt;h2&gt;
  
  
  Estimated Latency Gains
&lt;/h2&gt;

&lt;p&gt;A typical Synthadoc query against a mid-size wiki has two latency components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Phase 1 (BM25 retrieval):&lt;/strong&gt; 100–200ms. This runs regardless of cache.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phase 2 (LLM synthesis):&lt;/strong&gt; 2–10 seconds depending on provider, model, and answer length.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A cache hit skips Phase 2 entirely. The server reads the cached &lt;code&gt;result_json&lt;/code&gt; from SQLite (~0.26ms P50 on SSD via a persistent connection), then emits a synthetic SSE burst at full network speed. The client receives what looks like a live streamed response, but the entire burst completes in under 100ms instead of waiting 2–10 seconds for the LLM. With a reasoning model provider, that gap widens to 15–30 seconds per query, the cache makes those queries feel instant.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Cache Is Shared Across All Three Surfaces
&lt;/h2&gt;

&lt;p&gt;CLI, Obsidian plugin, and Web Chat UI all share the same &lt;code&gt;cache.db&lt;/code&gt;. If you ran &lt;code&gt;synthadoc query "..."&lt;/code&gt; from the CLI this morning and the wiki hasn't changed, opening the Obsidian modal and asking the same question will hit the cache. The key is identical - same normalized question, same epoch, same model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Drop the entire cache - both LLM response cache and query cache
&lt;span class="go"&gt;synthadoc cache clear
Cache cleared: 47 entries removed.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What Makes This Architecturally Different
&lt;/h2&gt;

&lt;p&gt;The caching architecture differs from the typical approach of setting an explicit TTL (cache for 24 hours, or cache for one week). TTL-based caches are almost always wrong at the edges: they're either too short (you evict answers that are still valid) or too long (you serve stale content after a wiki update). Epoch-based invalidation is event-driven — the cache is valid until something in the wiki changes, exactly. You don't think about expiry. You ingest new content, and the next query automatically goes to the LLM. Every query after that hits the cache again until the next change.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Demo
&lt;/h2&gt;

&lt;p&gt;Query caching is covered in the quick-start guide against the history-of-computing demo wiki:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Query caching:&lt;/strong&gt; &lt;a href="https://github.com/axoviq-ai/synthadoc/blob/main/docs/user-quick-start-guide.md#step-23--query-caching" rel="noopener noreferrer"&gt;Step 23 - Query caching&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full thing runs locally in about ten minutes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/axoviq-ai/synthadoc.git
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;".[dev]"&lt;/span&gt;
synthadoc &lt;span class="nb"&gt;install &lt;/span&gt;history-of-computing &lt;span class="nt"&gt;--target&lt;/span&gt; ~/wikis &lt;span class="nt"&gt;--demo&lt;/span&gt;
synthadoc plugin &lt;span class="nb"&gt;install &lt;/span&gt;history-of-computing
synthadoc web   &lt;span class="c"&gt;# opens browser&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you find Synthadoc useful, a ⭐ on GitHub helps the project reach more people: &lt;a href="https://github.com/axoviq-ai/synthadoc" rel="noopener noreferrer"&gt;https://github.com/axoviq-ai/synthadoc&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>llm</category>
      <category>architecture</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Paul Chen</dc:creator>
      <pubDate>Wed, 10 Jun 2026 02:56:51 +0000</pubDate>
      <link>https://dev.to/paul_chen_90371fe7426cb44/-3504</link>
      <guid>https://dev.to/paul_chen_90371fe7426cb44/-3504</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/paul_chen_90371fe7426cb44/synthadoc-streaming-queries-local-web-chat-and-a-self-invalidating-cache-1hgb" class="crayons-story__hidden-navigation-link"&gt;Synthadoc: Streaming Queries and a Local Web Chat UI&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/paul_chen_90371fe7426cb44" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3898190%2F38f30ba0-280f-4a52-8c7d-c773315b8da8.jpg" alt="paul_chen_90371fe7426cb44 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/paul_chen_90371fe7426cb44" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Paul Chen
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Paul Chen
                
                
              
              &lt;div id="story-author-preview-content-3851319" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/paul_chen_90371fe7426cb44" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3898190%2F38f30ba0-280f-4a52-8c7d-c773315b8da8.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Paul Chen&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/paul_chen_90371fe7426cb44/synthadoc-streaming-queries-local-web-chat-and-a-self-invalidating-cache-1hgb" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 8&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/paul_chen_90371fe7426cb44/synthadoc-streaming-queries-local-web-chat-and-a-self-invalidating-cache-1hgb" id="article-link-3851319"&gt;
          Synthadoc: Streaming Queries and a Local Web Chat UI
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/llm"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;llm&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/architecture"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;architecture&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/cli"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;cli&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/paul_chen_90371fe7426cb44/synthadoc-streaming-queries-local-web-chat-and-a-self-invalidating-cache-1hgb" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;8&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/paul_chen_90371fe7426cb44/synthadoc-streaming-queries-local-web-chat-and-a-self-invalidating-cache-1hgb#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            9 min read
          &lt;/small&gt;
          
          &lt;div class="only-sidebar-menu-item trusted-visible-block print-hidden align-center"&gt;
                
                    
                
          &lt;/div&gt;

        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>Synthadoc: Streaming Queries and a Local Web Chat UI</title>
      <dc:creator>Paul Chen</dc:creator>
      <pubDate>Mon, 08 Jun 2026 21:02:24 +0000</pubDate>
      <link>https://dev.to/paul_chen_90371fe7426cb44/synthadoc-streaming-queries-local-web-chat-and-a-self-invalidating-cache-1hgb</link>
      <guid>https://dev.to/paul_chen_90371fe7426cb44/synthadoc-streaming-queries-local-web-chat-and-a-self-invalidating-cache-1hgb</guid>
      <description>&lt;p&gt;There's a moment every Synthadoc user hits eventually. You've got forty or fifty compiled pages, a nightly ingest schedule running, lint keeping everything healthy. And then you open a terminal, type &lt;code&gt;synthadoc query "..."&lt;/code&gt;, and wait. The BM25 retrieval is instant. But then the cursor blinks. The LLM is thinking. You wait four seconds, six seconds, eight seconds. The answer eventually appears, all at once, like a curtain dropping.&lt;/p&gt;

&lt;p&gt;That wait is fine the first time. It gets annoying on the tenth query when you're in a research session and you already know the answer is coming - you just want to read it as it forms, not stare at a blinking cursor.&lt;/p&gt;

&lt;p&gt;v0.7.0 improves that. Streaming query output across all three query surfaces, and a local web chat UI that understands the health of your wiki. The architecture behind each of these turned out to be more interesting than I expected when we started building them. (A companion post covers the third feature in this release: a self-invalidating query cache.)&lt;/p&gt;




&lt;h2&gt;
  
  
  Diagram 1: What Changed in v0.7.0: The Architecture at a Glance
&lt;/h2&gt;

&lt;p&gt;The diagram below maps the full Synthadoc architecture as it stands after v0.7.0. Items marked &lt;strong&gt;[NEW]&lt;/strong&gt; are additions in this release; everything else was already present. The two features in this post touch two separate layers: the access layer gains a new client, and the engine gains new agents.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4tq2mjmw89xrlvabve24.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc9togei0k96n4m008nyx.png" alt=" " width="800" height="860"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two additions are the focus of this post:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Query Web UI&lt;/strong&gt; (Access Layer): the new &lt;code&gt;synthadoc web&lt;/code&gt; browser client using HTTP + SSE&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query (stream) · Action · Hint Engine&lt;/strong&gt; (Agents): streaming query pipeline, live command execution, deterministic hint generation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything flows through the same server process per wiki. The CLI, Obsidian plugin, and Web Chat UI are all thin clients talking to the same HTTP + SSE endpoint. There's no separate service for the web UI. The MCP Server (shown as optional with a dashed border) is a fourth access path for AI tools like Claude Desktop or Cursor — it exposes the same wiki operations over the Model Context Protocol and requires opt-in setup.&lt;/p&gt;




&lt;h2&gt;
  
  
  Three Ways to Query Your Wiki and When to Use Each
&lt;/h2&gt;

&lt;p&gt;Before getting into the streaming mechanics, it's worth laying out the three query surfaces Synthadoc now supports. All three can answer the same question, the difference is workflow fit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv1flol9w7xcvf100v4by.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fri7ld6eyqjpxrhgxdtsk.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  CLI: when the query is part of a larger workflow
&lt;/h3&gt;

&lt;p&gt;The CLI is where you go when a query isn't just a question, it's a step in something automated. The obvious case is CI/CD: a post-ingest job that queries the wiki to verify a newly compiled page before promoting it to active. Less obvious is using it as part of an agent integration, where an external orchestrator issues queries and parses the structured JSON output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Stream to terminal - tokens appear as the LLM generates them&lt;/span&gt;
synthadoc query &lt;span class="s2"&gt;"What were the main causes of the 2008 financial crisis?"&lt;/span&gt;

&lt;span class="c"&gt;# Script mode - waits for full response, stdout is clean for piping&lt;/span&gt;
synthadoc query &lt;span class="s2"&gt;"Summarize page: moore's-law"&lt;/span&gt; &lt;span class="nt"&gt;--no-stream&lt;/span&gt; | jq &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Force LLM call even if cache has a result - useful when wiki just changed&lt;/span&gt;
synthadoc query &lt;span class="s2"&gt;"What changed in the latest ingest?"&lt;/span&gt; &lt;span class="nt"&gt;--no-cache&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--no-stream&lt;/code&gt; flag is specifically for automation. Streaming output is beautiful on a terminal and disruptive in a pipeline. A script that parses &lt;code&gt;stdout&lt;/code&gt; doesn't want token-by-token delivery, it wants a complete JSON blob when the query is done. &lt;code&gt;--no-stream&lt;/code&gt; gives it that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Obsidian Plugin: when you're in a research session
&lt;/h3&gt;

&lt;p&gt;The Obsidian plugin exists for a different moment: you're writing a note, you need to check a claim against your wiki, and you don't want to leave Obsidian. The query modal (&lt;code&gt;Ctrl/Cmd+P → Synthadoc: Query: ask the wiki...&lt;/code&gt;) is the right tool here. It renders &lt;code&gt;[[wikilinks]]&lt;/code&gt; as clickable links, which means an answer that references related pages becomes navigable instantly.&lt;/p&gt;

&lt;p&gt;The streaming behaviour in the Obsidian plugin mirrors the CLI, tokens appear as they arrive, citations follow at the end. The bypass cache checkbox is visible in the modal, unchecked by default. For researchers doing active ingest sessions, checking it once gets you fresh output without reaching for the terminal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Web Chat UI: when you want a session, not a one-shot query
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;synthadoc web&lt;/code&gt; is the new entry. It opens a local chat interface in your browser, nothing leaves your machine, no cloud service, no authentication. It's designed for the kind of session that's too exploratory for the CLI and too long for the Obsidian modal.&lt;/p&gt;

&lt;p&gt;Each turn is an independent query - the same cache applies here as in the CLI and Obsidian plugin. The chat history is displayed in the browser, but prior messages are not yet injected into the LLM prompt; multi-turn context injection is planned for a future release.&lt;/p&gt;

&lt;p&gt;What the web UI adds over the other surfaces: operational commands. You can type "run lint", "show wiki status", "what pages are orphan pages?" or "schedule ingest every night at 9 PM" directly in the chat, and the Action Agent parses those and executes them live against your wiki, with results shown inline.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fytddsarsce76raaqaia6.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv2px7b948v4vlgx2jfrb.png" alt=" " width="800" height="537"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The screenshot above shows a live session against the history-of-computing demo wiki. The response to "What changed in the wiki this week?" includes a date-indexed ingest table, current lifecycle counts (Active: 80, Draft/Stale/Contradicted/Archived: all zero), and three action chips - "Activate a draft page", "Archive a stale page", "Restore an archived page to draft" - rendered inline as clickable buttons. The left panel shows prior session queries, allowing you to jump back into an earlier thread.&lt;/p&gt;




&lt;h2&gt;
  
  
  Streaming: The Architecture Behind a Two-Phase Response
&lt;/h2&gt;

&lt;p&gt;Every Synthadoc query goes through two phases. Phase 1 is retrieval: BM25 search, routing, sub-question decomposition if needed. This is synchronous and fast, typically 100–200ms. Phase 2 is synthesis: the LLM generates an answer from the retrieved pages. This is where the latency lives.&lt;/p&gt;

&lt;p&gt;The decision to stream only Phase 2 was deliberate. Phase 1 finishes before the first LLM token could possibly arrive, there's no partial retrieval state worth exposing. So the SSE protocol is clean:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frlkbvullfvzkob50f18i.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fggn2nwfh7984idmp0vqa.png" alt=" " width="800" height="563"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;status&lt;/code&gt; events let the UI give immediate feedback. The user knows within 150ms whether the wiki found relevant pages or not before any LLM latency has accumulated. "sources: 3" in the synthesizing event tells them the answer is backed by three pages before they've read a single word of it.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;gap&lt;/code&gt; event fires only when the wiki doesn't have enough to answer confidently. Instead of a vague "I don't know," it returns &lt;code&gt;suggested_searches&lt;/code&gt; - concrete ingest strings the user can use to fill the gap. These are generated by a secondary LLM call that decomposes the original question into targeted search queries - the same decomposition that drives sub-question retrieval, reused here to produce actionable ingest suggestions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Provider Streaming Behavior
&lt;/h3&gt;

&lt;p&gt;Not all providers stream in the same sense. API-based providers - OpenAI, Anthropic, Gemini, Ollama - emit tokens as they are generated, so the CLI and web UI render them character-by-character in real time. The latency shown in the SSE sequence above (one token every ~20ms) is what these providers deliver.&lt;/p&gt;

&lt;p&gt;CLI subprocess providers - &lt;strong&gt;Claude Code&lt;/strong&gt; (&lt;code&gt;claude-code&lt;/code&gt;) and &lt;strong&gt;Opencode&lt;/strong&gt; (&lt;code&gt;opencode&lt;/code&gt;) - work differently. They run as child processes and write their output only when the process exits, so there is no per-token stream to intercept. Synthadoc runs the subprocess to completion, then emits the result word-by-word through the same SSE pipe. The words arrive in a rapid burst rather than a gradual flow - the total wait is the same, but the perceived streaming effect is a short pause followed by the full answer appearing almost at once.&lt;/p&gt;

&lt;p&gt;If you are using a CLI subprocess provider and queries are timing out, increase the default timeout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;synthadoc query &lt;span class="s2"&gt;"..."&lt;/span&gt; &lt;span class="nt"&gt;--timeout&lt;/span&gt; 180
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default is 60 seconds, which is sufficient for API providers but may be short for subprocess providers on complex queries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Session Management
&lt;/h3&gt;

&lt;p&gt;Sessions live server-side in &lt;code&gt;audit.db&lt;/code&gt;, in two tables: &lt;code&gt;chat_sessions&lt;/code&gt; and &lt;code&gt;chat_messages&lt;/code&gt;. The React UI stores only the &lt;code&gt;session_id&lt;/code&gt; in memory - it's React state, not localStorage. This means sessions don't survive page reload, and every new browser tab starts fresh. This is a deliberate design choice: a session is tied to one exploratory thread, not your entire browsing history.&lt;/p&gt;

&lt;p&gt;Chat messages are stored to &lt;code&gt;audit.db&lt;/code&gt; after each turn, but prior messages are not yet injected into the LLM prompt, each query is answered independently. The session record is used for mode persistence and hint rotation, not for conversational context. Multi-turn prompt injection is planned for a future release.&lt;/p&gt;

&lt;h3&gt;
  
  
  Diagram 2: Web Query Flow: Client to Server, Session to Stream
&lt;/h3&gt;

&lt;p&gt;The diagram below traces a complete web UI query round-trip, from the user typing a question to the hint chips updating after the response. The left column is the browser; the right column is the server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh5pnjtkgom6f0hjl83ek.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbfemx02abcsvvnx2f453.png" alt=" " width="800" height="950"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few things worth highlighting in this flow. The &lt;code&gt;session_id&lt;/code&gt; lives only in React state - close the tab and it's gone. The mode determined at &lt;code&gt;POST /sessions&lt;/code&gt; (step 1) persists for the lifetime of that tab and shapes hint generation at every &lt;code&gt;done&lt;/code&gt; event (step 3 and 4). The HintEngine never calls the LLM - it reads the answer content and the session mode and applies deterministic rules to generate the three chips.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adaptive Hints: No LLM Required
&lt;/h3&gt;

&lt;p&gt;The hint chips - three clickable suggestions rendered below the chat input - update after every response. They're generated by a deterministic &lt;code&gt;HintEngine&lt;/code&gt;, not an LLM. No API call, no extra cost.&lt;/p&gt;

&lt;p&gt;The engine first classifies the wiki's health state when the session is created:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;Initial hints&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NEW_WIKI&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fewer than 5 pages&lt;/td&gt;
&lt;td&gt;Guide user toward first ingest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;EXPLORER&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;First session, healthy wiki&lt;/td&gt;
&lt;td&gt;Offer tour queries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HEALTH_CHECK&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Stale or contradicted pages exist&lt;/td&gt;
&lt;td&gt;Surface lint and lifecycle actions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POWER_USER&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Returning user, healthy wiki&lt;/td&gt;
&lt;td&gt;Context-sensitive topic suggestions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;After each assistant response, the &lt;code&gt;done&lt;/code&gt; SSE event carries a &lt;code&gt;next_hints&lt;/code&gt; array, three suggestions computed from the answer content and session mode. If the answer mentioned a specific page, the hints might suggest a follow-up on a related page. If the answer triggered a knowledge gap, the hints offer the &lt;code&gt;suggested_searches&lt;/code&gt; as clickable options.&lt;/p&gt;

&lt;p&gt;The design principle here is that hints should reflect where you are in the conversation, not where you were when you opened the browser. A user on a HEALTH_CHECK session who just asked about contradicted pages shouldn't see generic "try querying about X" chips, they should see "run lint", "list orphan pages", "archive contradicted page". The mode carries through the session, shaping every hint update.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Makes This Architecturally Different
&lt;/h2&gt;

&lt;p&gt;Most streaming chat interfaces work the same way: user sends a message, server calls the LLM, tokens stream back. There's no retrieval, no structured knowledge, no notion of whether the answer is backed by reviewed sources.&lt;/p&gt;

&lt;p&gt;Synthadoc's streaming pipeline is a two-phase system where the first phase is a structured knowledge retrieval against a compiled, lifecycle-tracked wiki. The tokens you receive as they arrive are not hallucinated filler - they're synthesized from pages that passed lint, have known provenance, and carry a lifecycle state that tells you when they were last reviewed. The &lt;code&gt;sources: N&lt;/code&gt; in the status event isn't decorative. It tells you before the first word of the answer how much of your wiki was relevant.&lt;/p&gt;

&lt;p&gt;The session mode detection adds something I haven't seen elsewhere: the server classifies your wiki's health state when you open a session and uses that classification to shape every hint update for the rest of the session. A HEALTH_CHECK session doesn't give you generic "explore your wiki" prompts, it gives you "these pages need attention." The hints aren't cosmetic. They're a live triage system for wiki health.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Demo
&lt;/h2&gt;

&lt;p&gt;The CLI streaming and web UI are covered in the quick-start guide against the history-of-computing demo wiki:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CLI streaming:&lt;/strong&gt; &lt;a href="https://github.com/axoviq-ai/synthadoc/blob/main/docs/user-quick-start-guide.md#step-5--query-the-pre-built-wiki-cli--obsidian" rel="noopener noreferrer"&gt;Step 5 - Query the pre-built wiki&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web Chat UI:&lt;/strong&gt; &lt;a href="https://github.com/axoviq-ai/synthadoc/blob/main/docs/user-quick-start-guide.md#step-22--use-the-web-chat-ui" rel="noopener noreferrer"&gt;Step 22 - Use the web chat UI&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full thing runs locally in about ten minutes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/axoviq-ai/synthadoc.git
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;".[dev]"&lt;/span&gt;
synthadoc &lt;span class="nb"&gt;install &lt;/span&gt;history-of-computing &lt;span class="nt"&gt;--target&lt;/span&gt; ~/wikis &lt;span class="nt"&gt;--demo&lt;/span&gt;
synthadoc plugin &lt;span class="nb"&gt;install &lt;/span&gt;history-of-computing
synthadoc web   &lt;span class="c"&gt;# opens browser&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you find Synthadoc useful, a ⭐ on GitHub helps the project reach more people: &lt;a href="https://github.com/axoviq-ai/synthadoc" rel="noopener noreferrer"&gt;https://github.com/axoviq-ai/synthadoc&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>architecture</category>
      <category>cli</category>
    </item>
  </channel>
</rss>
