<?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: Rost</title>
    <description>The latest articles on DEV Community by Rost (@rosgluk).</description>
    <link>https://dev.to/rosgluk</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%2F3544400%2F04dd81bf-749e-4055-971f-316c0134e76c.jpg</url>
      <title>DEV Community: Rost</title>
      <link>https://dev.to/rosgluk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rosgluk"/>
    <language>en</language>
    <item>
      <title>Trending Open Source AI Projects on GitHub, October 2026</title>
      <dc:creator>Rost</dc:creator>
      <pubDate>Wed, 21 Oct 2026 09:34:50 +0000</pubDate>
      <link>https://dev.to/rosgluk/trending-open-source-ai-projects-on-github-october-2026-4d42</link>
      <guid>https://dev.to/rosgluk/trending-open-source-ai-projects-on-github-october-2026-4d42</guid>
      <description>&lt;p&gt;GitHub's monthly trending page works for a language, because a language filter already defines the set. Open source AI asks a different question: the work spans five languages, and the useful cut is the job a repository does.&lt;/p&gt;

&lt;p&gt;This snapshot was read from the public GitHub API on 16 October 2026, and inside each group the repositories are ordered by star count. Stars measure attention, and a skills folder can gather that attention faster than a serving engine, so the group comes before the sort. The license column is part of the ranking as well, because several of the most starred projects are fair-code, brand-restricted, or split across two licenses.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.glukhov.org/developer-tools/comparisons/most-popular-rust-projects-on-github/" rel="noopener noreferrer"&gt;Rust&lt;/a&gt; and &lt;a href="https://www.glukhov.org/developer-tools/comparisons/most-popular-go-projects-on-github/" rel="noopener noreferrer"&gt;Go&lt;/a&gt; roundups on this site were built from that same page, with a language filter doing the grouping. Here the eight job groups below replace the filter, and each section states the trade that separates the close options. The closing sections turn the tables into a shortlist you can run, and into a repeatable method for the next snapshot.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to rank open source AI projects
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Search topics, then assign a job
&lt;/h3&gt;

&lt;p&gt;GitHub topics are the workable substitute for a language filter. Sort a topic such as &lt;code&gt;ai-agents&lt;/code&gt;, &lt;code&gt;llm&lt;/code&gt;, &lt;code&gt;rag&lt;/code&gt;, &lt;code&gt;local-llm&lt;/code&gt;, or &lt;code&gt;agent-memory&lt;/code&gt; by stars, and keep repositories that were pushed in the last few months. Free-text search for "AI" is too wide, and the unfiltered trending page still mixes agents with unrelated tools.&lt;/p&gt;

&lt;p&gt;Three public inputs are enough to rebuild the list. Use topic search for the pool, a scenario catalog such as &lt;a href="https://topopensource.ai/" rel="noopener noreferrer"&gt;Top Open Source AI&lt;/a&gt; for the job labels, and the repository &lt;code&gt;LICENSE&lt;/code&gt; file for the last column. That catalog publishes a composite of its own: stars at 70 percent, contributors at 10 percent, and update recency at 20 percent. This article keeps the simpler rule, so the number in the table is the number on GitHub.&lt;/p&gt;

&lt;p&gt;Useful starting queries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/topics/ai-agents?o=desc&amp;amp;s=stars" rel="noopener noreferrer"&gt;ai-agents topic, by stars&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/topics/llm?o=desc&amp;amp;s=stars" rel="noopener noreferrer"&gt;llm topic, by stars&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/topics/rag?o=desc&amp;amp;s=stars" rel="noopener noreferrer"&gt;rag topic, by stars&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/search?q=topic%3Aagent-memory+stars%3A%3E5000&amp;amp;type=repositories&amp;amp;s=stars&amp;amp;o=desc" rel="noopener noreferrer"&gt;agent-memory topic above 5,000 stars&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/trending?since=monthly" rel="noopener noreferrer"&gt;GitHub trending, monthly&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Keep software, and set lists aside
&lt;/h3&gt;

&lt;p&gt;A repository earns a row when someone can install it, or when it is a maintained harness that changes how an agent runs. Awesome-lists, prompt directories, and dumps of vendor system prompts collect stars and still leave nothing to deploy. They belong in a reading list, and they stay out of the tables below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add a trending column only from your own snapshots
&lt;/h3&gt;

&lt;p&gt;The language roundups could show stars gained in a month because GitHub already computes that delta inside one language. Across these groups, a monthly delta needs two snapshots of your own. Save &lt;code&gt;full_name&lt;/code&gt; and &lt;code&gt;stargazers_count&lt;/code&gt; on the first of the month, then subtract. Until that series exists, total stars are the honest ranking key. "Trending" in the title means actively maintained projects with the attention to match, as of this date.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
  topics[Topic search on GitHub]
  catalog[Scenario catalog]
  licenseFile[License file]
  drop[Set lists and prompt dumps aside]
  group[Assign each repo to one job]
  rank[Sort by stars inside the job]
  topics --&amp;gt; drop
  catalog --&amp;gt; group
  drop --&amp;gt; group
  group --&amp;gt; rank
  licenseFile --&amp;gt; rank&lt;/code&gt;&lt;/pre&gt;



&lt;h2&gt;
  
  
  Where the projects sit
&lt;/h2&gt;

&lt;p&gt;These groups are layers you can stack. A coding assistant can call a gateway, a gateway can call Ollama or vLLM, and a memory service can sit beside either of them. Two products from the same layer are usually a second opinion, and they are rarely a better architecture.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TB
  agent[Personal agent]
  coder[Coding assistant]
  skills[Skills and harness]
  memory[Memory]
  docs[RAG and documents]
  flow[Workflow builder]
  gate[Model gateway]
  run[Local runtime]
  coder --&amp;gt; skills
  agent --&amp;gt; memory
  coder --&amp;gt; memory
  agent --&amp;gt; docs
  flow --&amp;gt; docs
  agent --&amp;gt; gate
  coder --&amp;gt; gate
  flow --&amp;gt; gate
  docs --&amp;gt; run
  gate --&amp;gt; run&lt;/code&gt;&lt;/pre&gt;



&lt;h2&gt;
  
  
  Personal AI assistants
&lt;/h2&gt;

&lt;p&gt;This group is for agents that take actions on a machine or across tools, beyond a single coding session. OpenClaw leads by a wide margin. Hermes is the other general agent at this scale, and AutoGPT is the older name that is still accumulating stars while its license has split. A deeper leaderboard for this pair alone, with downloads, token share, and CVE history, is the &lt;a href="https://www.glukhov.org/ai-systems/comparisons/openclaw-hermes-alternatives-popularity/" rel="noopener noreferrer"&gt;OpenClaw vs Hermes Agent comparison&lt;/a&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;Stars&lt;/th&gt;
&lt;th&gt;License&lt;/th&gt;
&lt;th&gt;Repository&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OpenClaw&lt;/td&gt;
&lt;td&gt;392,126&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;openclaw/openclaw&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hermes Agent&lt;/td&gt;
&lt;td&gt;254,122&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;NousResearch/hermes-agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Firecrawl&lt;/td&gt;
&lt;td&gt;191,908&lt;/td&gt;
&lt;td&gt;AGPL-3.0&lt;/td&gt;
&lt;td&gt;firecrawl/firecrawl&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AutoGPT&lt;/td&gt;
&lt;td&gt;187,598&lt;/td&gt;
&lt;td&gt;MIT and Polyform Shield&lt;/td&gt;
&lt;td&gt;Significant-Gravitas/AutoGPT&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Firecrawl is listed here because agents need pages, and it is the web-data project readers actually star. It is a tool, and the assistants call it.&lt;/p&gt;

&lt;h3&gt;
  
  
  OpenClaw
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/openclaw/openclaw" rel="noopener noreferrer"&gt;openclaw/openclaw&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;392,126&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/openclaw/openclaw" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt; is a personal agent meant to run on your own operating system and carry out tasks, which is the plain reading of a project that calls itself an AI that does things on any platform. The star count is the largest in this article, and the MIT license is the permissive end of the set. Treat the install as a local automation runtime with broad access to the machine, and scope what it is allowed to touch before you leave it running.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hermes Agent
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/NousResearch/hermes-agent" rel="noopener noreferrer"&gt;NousResearch/hermes-agent&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;254,122&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/NousResearch/hermes-agent" rel="noopener noreferrer"&gt;Hermes Agent&lt;/a&gt;, from Nous Research, is a general agent designed to accumulate skills and context as it is used. It sits in the same job as OpenClaw, with a Python codebase and the same MIT terms. Choose it when you want that growth loop and you are already comfortable operating a Python agent. Running both Hermes and OpenClaw means two permission models to audit.&lt;/p&gt;

&lt;h3&gt;
  
  
  AutoGPT
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/Significant-Gravitas/AutoGPT" rel="noopener noreferrer"&gt;Significant-Gravitas/AutoGPT&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;187,598&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;MIT outside autogpt_platform; Polyform Shield inside it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/Significant-Gravitas/AutoGPT" rel="noopener noreferrer"&gt;AutoGPT&lt;/a&gt; is the project that made autonomous agents a mainstream GitHub category, and it is still one of the most starred names in the set. The license is no longer a single grant. Everything outside the &lt;code&gt;autogpt_platform&lt;/code&gt; folder is MIT, and everything inside that folder is under the &lt;a href="https://polyformshield.com/" rel="noopener noreferrer"&gt;Polyform Shield License&lt;/a&gt;, which permits use but reserves the code's commercial value to the producer. Read that split before you build a product on the platform directory.&lt;/p&gt;

&lt;h3&gt;
  
  
  Firecrawl
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/firecrawl/firecrawl" rel="noopener noreferrer"&gt;firecrawl/firecrawl&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;191,908&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;AGPL-3.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/firecrawl/firecrawl" rel="noopener noreferrer"&gt;Firecrawl&lt;/a&gt; turns sites into data that agents can call. The star count puts it among the assistants even though the job is acquisition, which says a lot about what people are wiring up in 2026. AGPL-3.0 matters if you modify it and offer the result as a service. For a personal agent that only calls a local or vendor endpoint, the license is a smaller constraint.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI coding assistants
&lt;/h2&gt;

&lt;p&gt;Coding agents are the densest part of the 2026 list. OpenCode leads the fully open terminal and editor agents, with Codex and Gemini CLI close behind as the vendor CLIs that happen to be open source. OpenHands, Cline, and Goose are the other programs you can actually run.&lt;/p&gt;

&lt;p&gt;Aider (49,567 stars, Apache-2.0), Continue (36,228, Apache-2.0), and Crush (28,669, FSL-1.1-MIT) are real coding tools in the next band. Crush's license is a &lt;a href="https://fsl.software/" rel="noopener noreferrer"&gt;Functional Source License&lt;/a&gt; that converts to MIT on the schedule written in the file. One coding agent is plenty. A second one is for a comparison week, and after that it is configuration you have to keep in sync.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;Stars&lt;/th&gt;
&lt;th&gt;License&lt;/th&gt;
&lt;th&gt;Repository&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OpenCode&lt;/td&gt;
&lt;td&gt;213,922&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;anomalyco/opencode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codex&lt;/td&gt;
&lt;td&gt;129,395&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;td&gt;openai/codex&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini CLI&lt;/td&gt;
&lt;td&gt;107,389&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;td&gt;google-gemini/gemini-cli&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenHands&lt;/td&gt;
&lt;td&gt;91,004&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;OpenHands/OpenHands&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cline&lt;/td&gt;
&lt;td&gt;70,370&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;td&gt;cline/cline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Goose&lt;/td&gt;
&lt;td&gt;55,324&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;td&gt;aaif-goose/goose&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  OpenCode
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/anomalyco/opencode" rel="noopener noreferrer"&gt;anomalyco/opencode&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;213,922&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/anomalyco/opencode" rel="noopener noreferrer"&gt;OpenCode&lt;/a&gt; is an open source coding agent with a terminal workflow and a large plugin surface. It has become the default name people cite when they want a coding agent they can inspect, and the MIT license matches that use. The repository is &lt;a href="https://github.com/anomalyco/opencode" rel="noopener noreferrer"&gt;anomalyco/opencode&lt;/a&gt;. Pick it when the agent itself should be something you can patch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Codex
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/openai/codex" rel="noopener noreferrer"&gt;openai/codex&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;129,395&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Rust&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/openai/codex" rel="noopener noreferrer"&gt;Codex&lt;/a&gt; is OpenAI's lightweight coding agent for the terminal, written in Rust and licensed Apache-2.0. It is the open client for a hosted model family, which is a different bargain from a local-first agent. Use it when your team already standardizes on OpenAI models and you want a CLI you can automate. The client being open does not move the model weights onto your disk.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gemini CLI
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/google-gemini/gemini-cli" rel="noopener noreferrer"&gt;google-gemini/gemini-cli&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;107,389&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/google-gemini/gemini-cli" rel="noopener noreferrer"&gt;Gemini CLI&lt;/a&gt; brings Gemini into the terminal as an agent, under Apache-2.0. It plays the same role as Codex for a different provider, and the TypeScript codebase is easier to embed in existing Node tooling. Choose it when Gemini is the model you already pay for. Running it beside Codex is reasonable only if each repo has one default agent.&lt;/p&gt;

&lt;h3&gt;
  
  
  OpenHands
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/OpenHands/OpenHands" rel="noopener noreferrer"&gt;OpenHands/OpenHands&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;91,004&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/OpenHands/OpenHands" rel="noopener noreferrer"&gt;OpenHands&lt;/a&gt; is a software-development agent with a full task loop, now published from the &lt;code&gt;OpenHands&lt;/code&gt; organization. The MIT license and the higher-level product make it a platform choice, closer to a hosted development environment you can self-host than to a single binary. Use it when you want issues, sandboxes, and a UI around the agent. For a one-person terminal workflow, OpenCode, Codex, or Goose is the smaller install.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cline
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/cline/cline" rel="noopener noreferrer"&gt;cline/cline&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;70,370&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/cline/cline" rel="noopener noreferrer"&gt;Cline&lt;/a&gt; is an autonomous coding agent that ships as an SDK, an IDE extension, or a CLI. Apache-2.0 and the editor integration make it the practical pick for people who live in VS Code and still want a library they can call. It overlaps OpenCode more than it overlaps a chat UI. Standardize on one of them per repository so reviews stay readable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Goose
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/aaif-goose/goose" rel="noopener noreferrer"&gt;aaif-goose/goose&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;55,324&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Rust&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/aaif-goose/goose" rel="noopener noreferrer"&gt;Goose&lt;/a&gt; is an extensible local agent that installs, edits, runs, and tests code with whatever model you point at it. The repository now lives at &lt;code&gt;aaif-goose/goose&lt;/code&gt;, and the Rust implementation plus Apache-2.0 make it a strong workstation agent. Choose Goose when the model provider should be swappable and the agent should run on the machine that holds the code. It is a quieter star count than OpenCode, and it is a more direct fit for a local model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agent skills and harnesses
&lt;/h2&gt;

&lt;p&gt;Topic search will put this group above several real products. Superpowers and ECC are instructions, hooks, and workflows for agents you already run. Spec Kit is GitHub's own process toolkit for spec-driven development. They change agent behavior, and they are still software you compose with a coding assistant from the previous section.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;Stars&lt;/th&gt;
&lt;th&gt;License&lt;/th&gt;
&lt;th&gt;Repository&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Superpowers&lt;/td&gt;
&lt;td&gt;299,886&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;obra/superpowers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ECC&lt;/td&gt;
&lt;td&gt;280,382&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;affaan-m/ECC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spec Kit&lt;/td&gt;
&lt;td&gt;141,439&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;github/spec-kit&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Superpowers
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/obra/superpowers" rel="noopener noreferrer"&gt;obra/superpowers&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;299,886&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Shell&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/obra/superpowers" rel="noopener noreferrer"&gt;Superpowers&lt;/a&gt; is an agentic skills framework and a software-development methodology, distributed mostly as shell and instruction files under MIT. The star count is what you get when a method spreads through coding-agent users in a few months. Install it on top of an agent you already trust, and expect to edit the skills to match the repository. The framework will follow whatever permissions that agent already has.&lt;/p&gt;

&lt;h3&gt;
  
  
  ECC
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/affaan-m/ECC" rel="noopener noreferrer"&gt;affaan-m/ECC&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;280,382&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;JavaScript&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/affaan-m/ECC" rel="noopener noreferrer"&gt;ECC&lt;/a&gt; is a harness for Claude Code, Codex, OpenCode, Cursor, and similar agents, covering skills, memory, and checks around how the agent works. It is the right shape if you want the same loop on more than one coding agent. It is also another moving layer between you and the model. Adopt it when the team has already picked the agents and wants one shared way to brief them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Spec Kit
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/github/spec-kit" rel="noopener noreferrer"&gt;github/spec-kit&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;141,439&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/github/spec-kit" rel="noopener noreferrer"&gt;Spec Kit&lt;/a&gt; is GitHub's toolkit for spec-driven development and the process around it. The maintainer is the platform most of these repositories already live on, and the MIT license is straightforward. Use it when you want a written spec before the agent edits code. It will stay quiet if the team treats the spec as optional paperwork.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI memory
&lt;/h2&gt;

&lt;p&gt;Memory is the layer that lets an agent resume. The starred projects split into two styles. claude-mem, Beads, and much of Hindsight attach memory to coding sessions. Mem0, Cognee, and Letta are services you call from application agents. Capture policy, dependencies, and self-hosting tradeoffs for these backends are compared in &lt;a href="https://www.glukhov.org/ai-systems/memory/agent-memory-providers/" rel="noopener noreferrer"&gt;Agent Memory Providers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Memvid (16,600 stars, Apache-2.0) and Zep (4,961 stars, Apache-2.0) are smaller options in the same job, Memvid as a single-file store and Zep as an integration-heavy memory API. One memory system per agent is the setup you can still explain later.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;Stars&lt;/th&gt;
&lt;th&gt;License&lt;/th&gt;
&lt;th&gt;Repository&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;claude-mem&lt;/td&gt;
&lt;td&gt;101,052&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;td&gt;thedotmack/claude-mem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mem0&lt;/td&gt;
&lt;td&gt;67,243&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;td&gt;mem0ai/mem0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hindsight&lt;/td&gt;
&lt;td&gt;50,055&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;vectorize-io/hindsight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cognee&lt;/td&gt;
&lt;td&gt;32,364&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;td&gt;topoteretes/cognee&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Beads&lt;/td&gt;
&lt;td&gt;27,917&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;gastownhall/beads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Letta&lt;/td&gt;
&lt;td&gt;25,156&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;td&gt;letta-ai/letta&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  claude-mem
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/thedotmack/claude-mem" rel="noopener noreferrer"&gt;thedotmack/claude-mem&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;101,052&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/thedotmack/claude-mem" rel="noopener noreferrer"&gt;claude-mem&lt;/a&gt; records what a coding agent does, compresses that history, and injects the relevant part into later sessions. It talks to Claude Code, OpenClaw, Codex, Gemini CLI, Hermes, Copilot, and OpenCode, which is why it leads this group. Apache-2.0 fits a sidecar you may want to modify. Use it when the thing you keep forgetting is the coding session itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mem0
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/mem0ai/mem0" rel="noopener noreferrer"&gt;mem0ai/mem0&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;67,243&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/mem0ai/mem0" rel="noopener noreferrer"&gt;Mem0&lt;/a&gt; is a memory layer for agents and applications, with user, session, and agent state behind an API. It is the default name when the product is a support bot or a personal assistant, and you want that memory outside any one coding tool. Apache-2.0 and a hosted option sit side by side, so self-hosting is a choice you can make later. Prefer it over a coding-session hook when many agents should share the same facts about a user.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hindsight
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/vectorize-io/hindsight" rel="noopener noreferrer"&gt;vectorize-io/hindsight&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;50,055&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/vectorize-io/hindsight" rel="noopener noreferrer"&gt;Hindsight&lt;/a&gt; is Vectorize's agent memory project, aimed at agents that learn from what they have already done, and it integrates with both coding agents and application frameworks. The MIT license is the most permissive in this group. The project publishes strong benchmark claims, and those claims should be re-run on your own transcripts before you bet a product on them. Pick Hindsight when you want a dedicated memory service and you are willing to compare it with Mem0 on your data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cognee
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/topoteretes/cognee" rel="noopener noreferrer"&gt;topoteretes/cognee&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;32,364&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/topoteretes/cognee" rel="noopener noreferrer"&gt;Cognee&lt;/a&gt; builds a memory and knowledge graph that agents can query, with an emphasis on running that layer on smaller models. It overlaps Mem0 and it also overlaps the RAG tools below, because a graph of your documents behaves like both memory and retrieval. Apache-2.0 keeps the embedding path replaceable. Choose it when the facts you care about are entities and relationships, and a flat vector index has already felt too lossy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Beads
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/gastownhall/beads" rel="noopener noreferrer"&gt;gastownhall/beads&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;27,917&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/gastownhall/beads" rel="noopener noreferrer"&gt;Beads&lt;/a&gt; stores coding-agent tasks, plans, and dependencies as files that travel with the git branch. The repository is now &lt;code&gt;gastownhall/beads&lt;/code&gt;, and it is the same project the January Go roundup listed under its earlier path. MIT and a git-backed store make it the memory tool that your existing review process can see. Use it when context should fork with the branch. Use claude-mem or Mem0 when the memory has to outlive a single repository.&lt;/p&gt;

&lt;h3&gt;
  
  
  Letta
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/letta-ai/letta" rel="noopener noreferrer"&gt;letta-ai/letta&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;25,156&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/letta-ai/letta" rel="noopener noreferrer"&gt;Letta&lt;/a&gt; is the stateful-agent platform that grew out of MemGPT. Memory here is part of the agent runtime, so the agent manages its own context as a feature of the platform. That is a deeper commitment than dropping Mem0 under an agent you already run. Apache-2.0 applies to the open platform. Choose Letta when you want the runtime and the memory designed together, and choose Mem0 when you want memory under agents you do not intend to replace.&lt;/p&gt;

&lt;h2&gt;
  
  
  RAG and knowledge bases
&lt;/h2&gt;

&lt;p&gt;RAG projects turn documents into a context layer. RAGFlow is the end-to-end engine people are starring. LlamaIndex is the document framework, GraphRAG is Microsoft's graph approach, and WeKnora is Tencent's knowledge platform, which also appeared in the January Go roundup and has kept growing.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;Stars&lt;/th&gt;
&lt;th&gt;License&lt;/th&gt;
&lt;th&gt;Repository&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;RAGFlow&lt;/td&gt;
&lt;td&gt;92,289&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;td&gt;infiniflow/ragflow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LlamaIndex&lt;/td&gt;
&lt;td&gt;52,513&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;run-llama/llama_index&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GraphRAG&lt;/td&gt;
&lt;td&gt;36,349&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;microsoft/graphrag&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WeKnora&lt;/td&gt;
&lt;td&gt;34,210&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;Tencent/WeKnora&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  RAGFlow
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/infiniflow/ragflow" rel="noopener noreferrer"&gt;infiniflow/ragflow&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;92,289&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/infiniflow/ragflow" rel="noopener noreferrer"&gt;RAGFlow&lt;/a&gt; is a self-hosted RAG engine that parses messy documents and exposes retrieval to agents, with citations as part of the product. Apache-2.0 and a UI you can hand to a team are why it leads this group by a clear gap. Use it when the job is "put our files behind an answer box" and you want the parser, the index, and the API from one project. Reach for LlamaIndex when you are embedding retrieval inside an application you already control.&lt;/p&gt;

&lt;h3&gt;
  
  
  LlamaIndex
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/run-llama/llama_index" rel="noopener noreferrer"&gt;run-llama/llama_index&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;52,513&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/run-llama/llama_index" rel="noopener noreferrer"&gt;LlamaIndex&lt;/a&gt; is a document-processing framework for agents and RAG pipelines. It is a library with many connectors, and the MIT license fits that role. You assemble the index, the store, and the agent yourself, which is the cost of the flexibility. Choose it for product code. Choose RAGFlow when you want the same job as a service you deploy.&lt;/p&gt;

&lt;h3&gt;
  
  
  GraphRAG
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/microsoft/graphrag" rel="noopener noreferrer"&gt;microsoft/graphrag&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;36,349&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/microsoft/graphrag" rel="noopener noreferrer"&gt;GraphRAG&lt;/a&gt; builds a graph over a corpus and answers questions by walking that graph, as Microsoft Research's open implementation of the idea. The technique itself is explained in &lt;a href="https://www.glukhov.org/rag/architecture/advanced-rag-variants-longrag-self-rag-graphrag/" rel="noopener noreferrer"&gt;Advanced RAG: LongRAG, Self-RAG and GraphRAG&lt;/a&gt;. MIT licensing makes it easy to try beside a vector pipeline. Graph construction costs more up front than a chunk-and-embed index, and it pays that cost back on questions that span many documents. Use it when the corpus has structure you are currently forcing into chunks.&lt;/p&gt;

&lt;h3&gt;
  
  
  WeKnora
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/Tencent/WeKnora" rel="noopener noreferrer"&gt;Tencent/WeKnora&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;34,210&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/Tencent/WeKnora" rel="noopener noreferrer"&gt;WeKnora&lt;/a&gt; is Tencent's knowledge platform for turning documents into a RAG index, an agent, and a wiki that the project describes as self-maintaining. The project license is MIT, with third-party components under their own terms. GitHub reports that license as Other, because the MIT grant sits under a Tencent preamble. It is the closest cousin to RAGFlow in this table, with a Go core. Prefer it when you want the wiki and the agent in the same box, and compare the parsers on a sample of your own PDFs before you migrate a library.&lt;/p&gt;

&lt;h2&gt;
  
  
  Workflow and agent builders
&lt;/h2&gt;

&lt;p&gt;Builders are where a team draws the flow, or where a developer imports the framework that draws it for them. n8n is the largest by stars and the narrowest by license. Dify and Langflow are the AI-native studios. LangChain is the library underneath a great many of the others.&lt;/p&gt;

&lt;p&gt;CrewAI (59,745 stars, MIT) and AutoGen (61,429 stars, CC-BY-4.0) belong in the same conversation as code-first multi-agent frameworks. AutoGen's Creative Commons attribution license is unusual for a code repository, and it is worth reading before you vendor it. LangChain remains the integration surface those frameworks still meet.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;Stars&lt;/th&gt;
&lt;th&gt;License&lt;/th&gt;
&lt;th&gt;Repository&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;n8n&lt;/td&gt;
&lt;td&gt;207,535&lt;/td&gt;
&lt;td&gt;Sustainable Use License&lt;/td&gt;
&lt;td&gt;n8n-io/n8n&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dify&lt;/td&gt;
&lt;td&gt;158,591&lt;/td&gt;
&lt;td&gt;Modified Apache-2.0&lt;/td&gt;
&lt;td&gt;langgenius/dify&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Langflow&lt;/td&gt;
&lt;td&gt;155,717&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;langflow-ai/langflow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LangChain&lt;/td&gt;
&lt;td&gt;147,886&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;langchain-ai/langchain&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  n8n
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/n8n-io/n8n" rel="noopener noreferrer"&gt;n8n-io/n8n&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;207,535&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;Sustainable Use License&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/n8n-io/n8n" rel="noopener noreferrer"&gt;n8n&lt;/a&gt; is a workflow platform with hundreds of integrations and native AI steps, and it is the project people mean when they want Zapier-style automation they can host. The &lt;a href="https://docs.n8n.io/choose-how-to-use-n8n/" rel="noopener noreferrer"&gt;Sustainable Use License&lt;/a&gt; allows internal business use, personal use, and non-commercial use. Distribution is limited to free, non-commercial copies, and files marked &lt;code&gt;.ee&lt;/code&gt; need a separate enterprise license. It is source-available fair-code, and it is still the right tool for internal automation if those limits match how you run it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dify
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/langgenius/dify" rel="noopener noreferrer"&gt;langgenius/dify&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;158,591&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;Modified Apache-2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/langgenius/dify" rel="noopener noreferrer"&gt;Dify&lt;/a&gt; is a workspace for agent workflows and RAG pipelines, with model providers and tools in one deployable stack. The license starts from Apache-2.0 and adds conditions: operating a multi-tenant service, meaning multiple workspaces, needs a commercial license, and the console logo and copyright notice stay on the frontend. Single-tenant internal use is the case the extra clauses leave open. Choose Dify when a team needs one UI from prototype to production, and choose Langflow when the license has to stay plain MIT.&lt;/p&gt;

&lt;h3&gt;
  
  
  Langflow
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/langflow-ai/langflow" rel="noopener noreferrer"&gt;langflow-ai/langflow&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;155,717&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/langflow-ai/langflow" rel="noopener noreferrer"&gt;Langflow&lt;/a&gt; is a visual builder for agents and RAG flows, with an MIT license and a star count essentially tied with Dify. The canvas is the product, and the generated flows are something a developer can still own. Use it when you want a diagram of the pipeline and a license a procurement checklist already understands. The trade is a younger application surface than Dify's workspace features.&lt;/p&gt;

&lt;h3&gt;
  
  
  LangChain
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/langchain-ai/langchain" rel="noopener noreferrer"&gt;langchain-ai/langchain&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;147,886&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/langchain-ai/langchain" rel="noopener noreferrer"&gt;LangChain&lt;/a&gt; is the agent-engineering library that most integration docs still assume. MIT and the breadth of connectors keep it in every shortlist, and the abstraction cost is real once the chain is doing something simple. Use it when you are writing application code and you want the adapters already written. Use Dify or Langflow when the deliverable is a workspace, and use a direct provider SDK when the flow is a handful of calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local inference and chat interfaces
&lt;/h2&gt;

&lt;p&gt;This is the layer under everything else. Ollama is the workstation runner, Transformers is the model-definition library, Open WebUI is the chat front end, llama.cpp is the portable C++ engine, and vLLM is the server engine. Hugging Face Transformers is included because so many of the other projects import it, even though you do not "run" it the way you run Ollama. API maturity, hardware support, and production readiness across the runtimes are compared in &lt;a href="https://www.glukhov.org/llm-hosting/comparisons/hosting-llms-ollama-localai-jan-lmstudio-vllm-comparison/" rel="noopener noreferrer"&gt;Ollama vs vLLM vs LM Studio&lt;/a&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;Stars&lt;/th&gt;
&lt;th&gt;License&lt;/th&gt;
&lt;th&gt;Repository&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ollama&lt;/td&gt;
&lt;td&gt;183,334&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;ollama/ollama&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transformers&lt;/td&gt;
&lt;td&gt;167,734&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;td&gt;huggingface/transformers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open WebUI&lt;/td&gt;
&lt;td&gt;155,825&lt;/td&gt;
&lt;td&gt;Open WebUI License&lt;/td&gt;
&lt;td&gt;open-webui/open-webui&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;llama.cpp&lt;/td&gt;
&lt;td&gt;131,311&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;ggml-org/llama.cpp&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;vLLM&lt;/td&gt;
&lt;td&gt;93,870&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;td&gt;vllm-project/vllm&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Ollama
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/ollama/ollama" rel="noopener noreferrer"&gt;ollama/ollama&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;183,334&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/ollama/ollama" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; runs current open models locally behind a simple CLI and an OpenAI-compatible API. It was already a leader in the January Go roundup, and at 183,334 stars it is still the default workstation runtime. MIT and a single daemon are the reasons. Use it on a developer machine and on small servers. Move the same weights to vLLM when concurrency, batching, and GPU utilization become the problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transformers
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/huggingface/transformers" rel="noopener noreferrer"&gt;huggingface/transformers&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;167,734&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/huggingface/transformers" rel="noopener noreferrer"&gt;Transformers&lt;/a&gt; is Hugging Face's library for defining, loading, and running models across text, vision, audio, and multimodal checkpoints. Apache-2.0 and the model hub around it make this the library the rest of the stack imports. You feel it when you are training, converting, or debugging a model. You can ignore it for a long time if Ollama or vLLM already serves the checkpoint you need.&lt;/p&gt;

&lt;h3&gt;
  
  
  Open WebUI
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/open-webui/open-webui" rel="noopener noreferrer"&gt;open-webui/open-webui&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;155,825&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;Open WebUI License&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/open-webui/open-webui" rel="noopener noreferrer"&gt;Open WebUI&lt;/a&gt; is the chat interface most people put in front of Ollama and OpenAI-compatible APIs. The &lt;a href="https://github.com/open-webui/open-webui/blob/main/LICENSE" rel="noopener noreferrer"&gt;Open WebUI License&lt;/a&gt; allows redistribution and then restricts branding. Above 50 end users in any rolling 30 days, you may not alter, remove, or replace the Open WebUI name and marks without written permission or an enterprise license. For a personal or small-team install that keeps the name, it is the polished front end. For a product you will rebrand, budget the enterprise conversation first. Docker deployment and the setup options are walked through in the &lt;a href="https://www.glukhov.org/llm-hosting/llm-frontends/open-webui-overview-quickstart-and-alternatives/" rel="noopener noreferrer"&gt;Open WebUI guide&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  llama.cpp
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/ggml-org/llama.cpp" rel="noopener noreferrer"&gt;ggml-org/llama.cpp&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;131,311&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;C++&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/ggml-org/llama.cpp" rel="noopener noreferrer"&gt;llama.cpp&lt;/a&gt; is the C and C++ inference engine that made local models practical on ordinary hardware. MIT licensing and the GGUF ecosystem are why Ollama and a crowd of smaller apps sit on top of it. Use the project directly when you want the engine, the quantization tools, or a binary with few dependencies. Use Ollama when you want that engine with model management already wrapped.&lt;/p&gt;

&lt;h3&gt;
  
  
  vLLM
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/vllm-project/vllm" rel="noopener noreferrer"&gt;vllm-project/vllm&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;93,870&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/vllm-project/vllm" rel="noopener noreferrer"&gt;vLLM&lt;/a&gt; is a high-throughput serving engine for GPUs, with continuous batching and an OpenAI-compatible API. Apache-2.0 and the deployment focus make it the server counterpart to Ollama. Star count is lower because the audience is people who run GPUs, and that audience is the one that feels the difference. Choose vLLM when more than a few concurrent sessions share one machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  LLM gateways
&lt;/h2&gt;

&lt;p&gt;A gateway is the OpenAI-compatible front door in front of many providers. LiteLLM is the common choice, New API is the self-hosted distributor, and Bifrost is the smaller high-throughput gateway. Put a gateway in the stack when you have more than one provider, a budget per key, or a need to fail over. A single local Ollama daemon does not need one.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;Stars&lt;/th&gt;
&lt;th&gt;License&lt;/th&gt;
&lt;th&gt;Repository&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LiteLLM&lt;/td&gt;
&lt;td&gt;61,338&lt;/td&gt;
&lt;td&gt;MIT for the core&lt;/td&gt;
&lt;td&gt;BerriAI/litellm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;New API&lt;/td&gt;
&lt;td&gt;50,037&lt;/td&gt;
&lt;td&gt;AGPL-3.0&lt;/td&gt;
&lt;td&gt;QuantumNous/new-api&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bifrost&lt;/td&gt;
&lt;td&gt;8,837&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;td&gt;maximhq/bifrost&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  LiteLLM
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/BerriAI/litellm" rel="noopener noreferrer"&gt;BerriAI/litellm&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;61,338&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;MIT outside enterprise/&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/BerriAI/litellm" rel="noopener noreferrer"&gt;LiteLLM&lt;/a&gt; exposes a large set of provider APIs through one OpenAI-shaped interface, with spend tracking, retries, and guardrails. Code outside the &lt;code&gt;enterprise/&lt;/code&gt; directory is MIT, and that directory has its own license. The Python SDK is what application code imports, and the proxy is what the platform team runs. Use the core when you want one client for many vendors. Check the enterprise directory before you assume the whole repository is MIT.&lt;/p&gt;

&lt;h3&gt;
  
  
  New API
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/QuantumNous/new-api" rel="noopener noreferrer"&gt;QuantumNous/new-api&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;50,037&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;AGPL-3.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/QuantumNous/new-api" rel="noopener noreferrer"&gt;New API&lt;/a&gt; is a model hub that aggregates providers and re-serves them as OpenAI, Claude, or Gemini compatible endpoints. It is the gateway people deploy when they also want user accounts, quotas, and a distribution UI. AGPL-3.0 is the term that matters if you modify the hub and offer it as a service. For an internal gateway with upstream keys, it is a concrete alternative to LiteLLM's proxy, with a Go deployment and a stricter license.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bifrost
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/maximhq/bifrost" rel="noopener noreferrer"&gt;maximhq/bifrost&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stars&lt;/td&gt;
&lt;td&gt;8,837&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/maximhq/bifrost" rel="noopener noreferrer"&gt;Bifrost&lt;/a&gt; is a Go gateway for models and tools, with failover, budgets, and an MCP gateway in the same process. The star count is far below LiteLLM, and the January Go roundup already had it on the trending list, so the project is younger than it is obscure. Apache-2.0 avoids the enterprise-directory split. Consider it when the gateway is on the request path of a busy service and you want a small Go binary. Keep LiteLLM when you need the long tail of provider adapters today.&lt;/p&gt;

&lt;h2&gt;
  
  
  A stack worth running
&lt;/h2&gt;

&lt;p&gt;The tables are a map, and a workstation still needs a short list. For a local setup in October 2026, Ollama plus one coding agent covers the daily loop. Add Open WebUI if the branding clause fits the user count, Mem0 or claude-mem depending on whether memory belongs to an application or to a coding session, and RAGFlow when the documents are the product. Add LiteLLM or New API only after a second model provider shows up.&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;Default pick&lt;/th&gt;
&lt;th&gt;Choose the alternative when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Runtime&lt;/td&gt;
&lt;td&gt;Ollama&lt;/td&gt;
&lt;td&gt;vLLM, once several users share a GPU server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chat UI&lt;/td&gt;
&lt;td&gt;Open WebUI&lt;/td&gt;
&lt;td&gt;You need to remove the brand above 50 users&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Coding agent&lt;/td&gt;
&lt;td&gt;OpenCode, Codex, Gemini CLI, or Goose&lt;/td&gt;
&lt;td&gt;You self-host the whole task loop: OpenHands&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory&lt;/td&gt;
&lt;td&gt;claude-mem or Mem0&lt;/td&gt;
&lt;td&gt;The facts are a graph: Cognee. The facts follow a git branch: Beads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Documents&lt;/td&gt;
&lt;td&gt;RAGFlow&lt;/td&gt;
&lt;td&gt;You are embedding retrieval in your own service: LlamaIndex&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Builder&lt;/td&gt;
&lt;td&gt;Langflow&lt;/td&gt;
&lt;td&gt;You accept Dify's multi-tenant clause, or n8n's fair-code terms for internal automation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gateway&lt;/td&gt;
&lt;td&gt;LiteLLM core&lt;/td&gt;
&lt;td&gt;You want AGPL and a user-facing hub: New API&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Skills packs such as Superpowers and ECC come after the agent is chosen. They are easy to star and easy to accumulate, and three harnesses on one repository will fight over the same instructions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping the ranking current
&lt;/h2&gt;

&lt;p&gt;Repeat the topic queries at the top of this article, assign each new repository to one job, and re-sort that job by stars. Then open the license file. A project that jumped the table on a skills directory, a prompt list, or a weekend of attention can wait for the next snapshot before it replaces a tool you run.&lt;/p&gt;

&lt;p&gt;Store the snapshot. A single CSV of &lt;code&gt;full_name&lt;/code&gt;, &lt;code&gt;stargazers_count&lt;/code&gt;, &lt;code&gt;license&lt;/code&gt;, and &lt;code&gt;pushed_at&lt;/code&gt;, taken monthly, turns the next edition of this page into a real trending column, the same kind of delta the language articles already show. Until then, the groups are the part that ages well, and the star counts are the part that goes stale on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;p&gt;Star counts, languages, and license identities were read from the GitHub repository API and the repository &lt;code&gt;LICENSE&lt;/code&gt; files on 16 October 2026. Job labels also draw on the public scenario list at Top Open Source AI, which documents its own composite score separately from the sort used here.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/topics/ai-agents?o=desc&amp;amp;s=stars" rel="noopener noreferrer"&gt;GitHub topics: ai-agents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/topics/llm?o=desc&amp;amp;s=stars" rel="noopener noreferrer"&gt;GitHub topics: llm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/topics/rag?o=desc&amp;amp;s=stars" rel="noopener noreferrer"&gt;GitHub topics: rag&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/trending?since=monthly" rel="noopener noreferrer"&gt;GitHub trending, monthly&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://topopensource.ai/" rel="noopener noreferrer"&gt;Top Open Source AI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://polyformshield.com/" rel="noopener noreferrer"&gt;Polyform Shield License&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fsl.software/" rel="noopener noreferrer"&gt;Functional Source License&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.n8n.io/choose-how-to-use-n8n/" rel="noopener noreferrer"&gt;n8n: Sustainable Use License and plans&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/open-webui/open-webui/blob/main/LICENSE" rel="noopener noreferrer"&gt;Open WebUI License file&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>opensource</category>
      <category>ai</category>
      <category>github</category>
      <category>llm</category>
    </item>
    <item>
      <title>Hindsight for Hermes Agent: Self-Hosted Memory with Local LLMs</title>
      <dc:creator>Rost</dc:creator>
      <pubDate>Sun, 18 Oct 2026 02:50:36 +0000</pubDate>
      <link>https://dev.to/rosgluk/hindsight-for-hermes-agent-self-hosted-memory-with-local-llms-1ke3</link>
      <guid>https://dev.to/rosgluk/hindsight-for-hermes-agent-self-hosted-memory-with-local-llms-1ke3</guid>
      <description>&lt;p&gt;A self-hosted AI agent needs more than a long context window. It needs a way to retain useful facts, retrieve earlier decisions, and revise what it thinks it knows as projects evolve.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.glukhov.org/ai-systems/hermes/" rel="noopener noreferrer"&gt;Hermes Agent&lt;/a&gt; ships with built-in memory files, but an external provider can provide much richer retrieval across sessions. &lt;a href="https://github.com/vectorize-io/hindsight" rel="noopener noreferrer"&gt;Hindsight&lt;/a&gt; is one of the more interesting options in that space. This guide installs it for Hermes with no cloud inference, first as an all-in-one Docker container and then against an existing PostgreSQL server with separate local model endpoints.&lt;/p&gt;

&lt;p&gt;The guide covers deployment, choosing and wiring local embedding, reranking, and generative models, controlling what auto-retain writes into the bank, connecting Hermes, and the operational layer: backup, migration, and day-to-day bank hygiene. The examples use a deliberate port scheme — &lt;code&gt;8080&lt;/code&gt; for the main llama.cpp server, &lt;code&gt;8081&lt;/code&gt; for embeddings, &lt;code&gt;8082&lt;/code&gt; for reranking, &lt;code&gt;11434&lt;/code&gt; for Ollama — so change them to the ports you already use and keep the small servers off the main model's port.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Hindsight Does
&lt;/h2&gt;

&lt;p&gt;Hindsight is a persistent agent memory engine with three principal operations: &lt;strong&gt;retain&lt;/strong&gt;, &lt;strong&gt;recall&lt;/strong&gt;, and &lt;strong&gt;reflect&lt;/strong&gt;. Retain extracts and stores information; recall combines semantic, keyword, graph, and temporal retrieval; reflect synthesizes an answer from accumulated memories. Background consolidation can turn overlapping observations into a more useful knowledge layer.&lt;/p&gt;

&lt;p&gt;It is not just an embeddings database. A vector store can locate similar passages, but a memory system also has to preserve chronology, project boundaries, contradictory evidence, and changes in decisions. Those failure modes are explored in &lt;a href="https://www.glukhov.org/ai-systems/memory/self-reinforcing-memory-loops/" rel="noopener noreferrer"&gt;self-reinforcing memory loops&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For the wider market, see &lt;a href="https://www.glukhov.org/ai-systems/memory/agent-memory-providers/" rel="noopener noreferrer"&gt;agent memory providers compared&lt;/a&gt;. Hermes' built-in &lt;code&gt;MEMORY.md&lt;/code&gt;, &lt;code&gt;USER.md&lt;/code&gt;, and memory modes are covered in the &lt;a href="https://www.glukhov.org/ai-systems/hermes/hermes-agent-memory-system/" rel="noopener noreferrer"&gt;Hermes Agent memory system&lt;/a&gt; guide, so this article assumes you know how the built-in layer works.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Move Beyond Mnemosyne
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.glukhov.org/ai-systems/hermes/mnemosyne-memory/" rel="noopener noreferrer"&gt;Mnemosyne for Hermes&lt;/a&gt; offers appealing local-first ideas, including SQLite storage and hybrid retrieval. In practice, installation constraints, version compatibility, write-approval behaviour, and rapid changes around its 4.0 prereleases can make it difficult to operate predictably. Those are operational concerns, not evidence that Mnemosyne's memory model is fundamentally flawed.&lt;/p&gt;

&lt;p&gt;Hindsight is not dependency-free either: its Docker image and local models can consume substantial disk space. Running its API as an independent service, with inference elsewhere, keeps upgrades and troubleshooting isolated from the agent itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose a Deployment
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;A: All-in-one container&lt;/th&gt;
&lt;th&gt;B: Existing PostgreSQL server&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hindsight API and UI&lt;/td&gt;
&lt;td&gt;Docker container&lt;/td&gt;
&lt;td&gt;Docker container&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;td&gt;Embedded &lt;code&gt;pg0&lt;/code&gt; in container&lt;/td&gt;
&lt;td&gt;Existing PostgreSQL machine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generative LLM&lt;/td&gt;
&lt;td&gt;External llama.cpp&lt;/td&gt;
&lt;td&gt;External llama.cpp&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Embeddings&lt;/td&gt;
&lt;td&gt;Default local or external&lt;/td&gt;
&lt;td&gt;External Ollama or llama.cpp&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reranking&lt;/td&gt;
&lt;td&gt;Default local or external&lt;/td&gt;
&lt;td&gt;External rerank server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&gt;Quick evaluation&lt;/td&gt;
&lt;td&gt;Long-running, isolated installation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here, "all-in-one" means the Hindsight API, UI, and database are in one container; the inference LLM is still hosted elsewhere. This is intentional: Docker's official Hindsight image does not bundle a ready-to-use local generative llama.cpp server.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    H[Hermes Agent] --&amp;gt;|Memory API| A[Hindsight API]
    A --&amp;gt; P[(PostgreSQL)]
    A --&amp;gt; E[Embedding endpoint]
    A --&amp;gt; R[Reranking endpoint]
    A --&amp;gt; L[Generative LLM endpoint]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Use LAN-reachable addresses, not &lt;code&gt;localhost&lt;/code&gt;, for services running on a different host. Restrict these ports to trusted clients; the example &lt;code&gt;dummy&lt;/code&gt; keys are compatibility placeholders, not authentication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option A: All-in-One Hindsight Container
&lt;/h2&gt;

&lt;p&gt;This is the simplest way to evaluate Hindsight without filling the Hermes VM with Python, Torch, and local model dependencies. The container runs Hindsight and embedded PostgreSQL, while your main llama.cpp LLM handles memory extraction and reflection.&lt;/p&gt;

&lt;p&gt;Two image tags matter here. &lt;code&gt;ghcr.io/vectorize-io/hindsight:latest&lt;/code&gt; is the large image — roughly 9 GB on AMD64 — because it bundles a BGE embedder, a MiniLM reranker, and embedded PostgreSQL. &lt;code&gt;latest-slim&lt;/code&gt; is roughly 500 MB and expects those two models to live elsewhere, which is what you want once you point Hindsight at the embedding and reranking setup in Serve and Wire Local Models. Once the health check passes, pin a specific tag: a moving tag is a poor match for a database you intend to keep.&lt;/p&gt;

&lt;p&gt;Create a directory and a Compose file on the Hindsight host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/services/hindsight
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/services/hindsight
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; compose.yaml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;YAML&lt;/span&gt;&lt;span class="sh"&gt;'
services:
  hindsight:
    image: ghcr.io/vectorize-io/hindsight:latest
    container_name: hindsight
    restart: unless-stopped
    shm_size: 1gb
    extra_hosts:
      - "host.docker.internal:host-gateway"
    ports:
      - "8888:8888"  # Memory API
      - "9999:9999"  # Control-plane UI
    environment:
      HINDSIGHT_API_WORKER_ID: hindsight-standalone
      HINDSIGHT_API_LLM_PROVIDER: openai
      HINDSIGHT_API_LLM_BASE_URL: http://WORKSTATION_IP:8080/v1
      HINDSIGHT_API_LLM_MODEL: LocalMegaLLM
      HINDSIGHT_API_LLM_API_KEY: dummy
      HINDSIGHT_API_LLM_MAX_CONCURRENT: "2"
    volumes:
      - hindsight-pg0:/home/hindsight/.pg0
volumes:
  hindsight-pg0:
&lt;/span&gt;&lt;span class="no"&gt;YAML
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;WORKSTATION_IP&lt;/code&gt; with the host running llama.cpp. On Linux, &lt;code&gt;host.docker.internal&lt;/code&gt; resolves only if the container declares the &lt;code&gt;host-gateway&lt;/code&gt; mapping shown above; without it, a base URL of &lt;code&gt;http://host.docker.internal:8080/v1&lt;/code&gt; points nowhere. The 1 GB shared-memory reservation is particularly useful for embedded PostgreSQL indexing; Docker's small default &lt;code&gt;/dev/shm&lt;/code&gt; can cause failures. A named volume preserves the database when the container is recreated, and a bind mount alternative has to be owned by uid 1000, the user inside the image.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;HINDSIGHT_API_WORKER_ID&lt;/code&gt; should stay stable across recreations — Docker's default is the container hostname, and a task that was mid-flight when a container died stays stuck under the old worker id.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;--tail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100
curl &lt;span class="nt"&gt;-f&lt;/span&gt; http://localhost:8888/health
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your installed Hindsight version exposes health at a different path, inspect &lt;code&gt;http://localhost:8888/docs&lt;/code&gt; and the container logs. The control plane is normally available at &lt;code&gt;http://HINDSIGHT_HOST:9999&lt;/code&gt;. Once the basic deployment works, you can also point its embedding and reranking providers at the configuration in Serve and Wire Local Models rather than loading the default models inside the container.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not expose ports 8888 or 9999 directly to the internet.&lt;/strong&gt; Use a private network or an authenticated reverse proxy. Embedded PostgreSQL is convenient for testing, but an externally managed database is easier to back up and maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option B: Hindsight Against an Existing PostgreSQL Server
&lt;/h2&gt;

&lt;p&gt;For a long-running installation, I prefer keeping the Hindsight API, database, and inference endpoints separate. The Hindsight VM can remain small because it runs orchestration and HTTP services rather than heavy models.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    H[Hermes VM] --&amp;gt;|port 8888| A[Hindsight VM]
    A --&amp;gt;|port 5432| P[(PostgreSQL host)]
    A --&amp;gt;|port 8080| L[Generative LLM]
    A --&amp;gt;|port 8081 or 11434| E[Embedding endpoint]
    A --&amp;gt;|port 8082| R[Reranking endpoint]&lt;/code&gt;&lt;/pre&gt;



&lt;h3&gt;
  
  
  Prepare PostgreSQL and pgvector
&lt;/h3&gt;

&lt;p&gt;Hindsight needs PostgreSQL 14 or newer and a vector extension in the &lt;code&gt;hindsight&lt;/code&gt; database. The extension is per database: installing the package on the server does nothing until &lt;code&gt;CREATE EXTENSION&lt;/code&gt; runs there. On a Debian or Ubuntu PostgreSQL server, install the &lt;code&gt;pgvector&lt;/code&gt; package matching the actual PostgreSQL major version; for example, on PostgreSQL 16:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;postgresql-16-pgvector
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The package name and availability depend on your configured apt repositories. If it is not available, follow the &lt;a href="https://github.com/pgvector/pgvector" rel="noopener noreferrer"&gt;pgvector installation instructions&lt;/a&gt;, or install a compatible package from the PostgreSQL PGDG repository. Do not blindly change the PostgreSQL major version on a shared server.&lt;/p&gt;

&lt;p&gt;Create a dedicated role and database as a PostgreSQL administrator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;hindsight&lt;/span&gt; &lt;span class="n"&gt;LOGIN&lt;/span&gt; &lt;span class="n"&gt;PASSWORD&lt;/span&gt; &lt;span class="s1"&gt;'REPLACE_WITH_LONG_PASSWORD'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;hindsight&lt;/span&gt; &lt;span class="k"&gt;OWNER&lt;/span&gt; &lt;span class="n"&gt;hindsight&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Connect to the new database and enable the extension:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; postgres psql &lt;span class="nt"&gt;-d&lt;/span&gt; hindsight &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'CREATE EXTENSION IF NOT EXISTS vector;'&lt;/span&gt;
&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; postgres psql &lt;span class="nt"&gt;-d&lt;/span&gt; hindsight &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"SELECT extname, extversion FROM pg_extension WHERE extname = 'vector';"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The extension normally needs elevated privileges to install; the application role does not need to be a superuser. Hindsight uses pgvector by default and wants 0.8.0 or newer for iterative ANN scans; older servers reject that setting. &lt;code&gt;HINDSIGHT_API_VECTOR_EXTENSION&lt;/code&gt; can alternatively select &lt;code&gt;pgvectorscale&lt;/code&gt; (a DiskANN option Hindsight documents for very large banks) or &lt;code&gt;vchord&lt;/code&gt;, both of which need extra packaging on this exact PostgreSQL build. Switching algorithms after memories exist stops startup and asks for a migration, so choose before the first retain. For a personal Hermes bank, pgvector is the one to install and then stop.&lt;/p&gt;

&lt;p&gt;Allow only the Hindsight host through &lt;code&gt;pg_hba.conf&lt;/code&gt;, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;host  hindsight  hindsight  192.168.1.50/32  scram-sha-256
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set PostgreSQL &lt;code&gt;listen_addresses&lt;/code&gt; appropriately and use TLS or a trusted private network for cross-host connections. Back up this database independently of the Hindsight Docker container. If the server is shared with other applications, keep Hindsight in its own database, or set &lt;code&gt;HINDSIGHT_API_DATABASE_SCHEMA&lt;/code&gt; to a schema you own; the default schema is &lt;code&gt;public&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run Hindsight Against PostgreSQL
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;compose.yaml&lt;/code&gt; on the Hindsight VM. This version deliberately does not start PostgreSQL locally:&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;hindsight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/vectorize-io/hindsight:latest-slim&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8888:8888"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9999:9999"&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.env&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;HINDSIGHT_API_LLM_PROVIDER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openai&lt;/span&gt;
      &lt;span class="na"&gt;HINDSIGHT_API_LLM_BASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://WORKSTATION_IP:8080/v1&lt;/span&gt;
      &lt;span class="na"&gt;HINDSIGHT_API_LLM_MODEL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LocalMegaLLM&lt;/span&gt;
      &lt;span class="na"&gt;HINDSIGHT_API_LLM_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dummy&lt;/span&gt;
      &lt;span class="na"&gt;HINDSIGHT_API_LLM_MAX_CONCURRENT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2"&lt;/span&gt;
      &lt;span class="na"&gt;HINDSIGHT_API_DB_POOL_MIN_SIZE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2"&lt;/span&gt;
      &lt;span class="na"&gt;HINDSIGHT_API_DB_POOL_MAX_SIZE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10"&lt;/span&gt;
      &lt;span class="na"&gt;HINDSIGHT_API_WORKER_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hindsight-standalone&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Store the external database URL in a file called &lt;code&gt;.env&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HINDSIGHT_API_DATABASE_URL=postgresql://hindsight:YOUR_PASSWORD@POSTGRES_IP:5432/hindsight
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;600 .env
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
docker compose logs &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;--tail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your password contains &lt;code&gt;@&lt;/code&gt;, &lt;code&gt;:&lt;/code&gt;, &lt;code&gt;/&lt;/code&gt;, or other reserved URL characters, URL-encode it before putting it in the connection string. Test that the Hindsight VM can reach PostgreSQL port 5432, and that &lt;code&gt;pg_hba.conf&lt;/code&gt; allows the Hindsight host — a server bound only to &lt;code&gt;127.0.0.1&lt;/code&gt; refuses the connection even when the URL looks right. Allow the first startup to run database migrations; migrations run on startup unless you set &lt;code&gt;HINDSIGHT_API_RUN_MIGRATIONS_ON_STARTUP=false&lt;/code&gt;. With the API and database up, continue with Choose Models for Hindsight and Serve and Wire Local Models before connecting Hermes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose Models for Hindsight
&lt;/h2&gt;

&lt;p&gt;Deploy puts the API and database on the network. The next decision is which models fill the three inference roles: embeddings for recall candidates, a reranker to re-order them, and a generative LLM for retain, reflect, and consolidation. A practical default for a CPU Hindsight host whose GPU is already busy with conversation is in-process &lt;code&gt;Qwen/Qwen3-Embedding-0.6B&lt;/code&gt;, in-process &lt;code&gt;BAAI/bge-reranker-v2-m3&lt;/code&gt;, and a shared llama.cpp chat model for generative work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Embedding models
&lt;/h3&gt;

&lt;p&gt;Hindsight reads the embedding dimension on startup and sizes the bank to match, so the model you pick at first retain is the model the bank keeps until you rebuild it. Dimensionality and instruction behaviour matter more than leaderboard rank: a stronger 8B embedder is not worth a migration if the bank is already a 384- or 1024-dimensional one. Rows below are sorted from lightest to heaviest; RAM figures are approximate loaded footprint on CPU (FP16 / typical local load), not GGUF file size.&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;Params&lt;/th&gt;
&lt;th&gt;Dim&lt;/th&gt;
&lt;th&gt;Context&lt;/th&gt;
&lt;th&gt;Loaded&lt;/th&gt;
&lt;th&gt;Runs on&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BAAI/bge-small-en-v1.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;33M&lt;/td&gt;
&lt;td&gt;384&lt;/td&gt;
&lt;td&gt;512&lt;/td&gt;
&lt;td&gt;~100–130 MB&lt;/td&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;td&gt;Hindsight default; ships in &lt;code&gt;hindsight:latest&lt;/code&gt;. Fastest local path. Score distribution still calibrates many &lt;code&gt;recall_min_scores&lt;/code&gt; defaults after you leave it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BAAI/bge-base-en-v1.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;109M&lt;/td&gt;
&lt;td&gt;768&lt;/td&gt;
&lt;td&gt;512&lt;/td&gt;
&lt;td&gt;~400 MB&lt;/td&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;td&gt;Mid-tier English BGE; locks a 768-dim bank.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~118M&lt;/td&gt;
&lt;td&gt;384&lt;/td&gt;
&lt;td&gt;512&lt;/td&gt;
&lt;td&gt;~200 MB&lt;/td&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;td&gt;Hindsight-documented multilingual MiniLM; 50+ languages at the same 384-dim bank width as BGE-small.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;intfloat/multilingual-e5-small&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~118M&lt;/td&gt;
&lt;td&gt;384&lt;/td&gt;
&lt;td&gt;512&lt;/td&gt;
&lt;td&gt;~200 MB&lt;/td&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;td&gt;Default for the &lt;code&gt;onnx&lt;/code&gt; provider; light multilingual CPU path when you have an ONNX export.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nomic-ai/nomic-embed-text-v1.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~137M&lt;/td&gt;
&lt;td&gt;768&lt;/td&gt;
&lt;td&gt;8k&lt;/td&gt;
&lt;td&gt;~500 MB&lt;/td&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;td&gt;Common Ollama embedder; longer context than classic BGE, still English-first.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;google/embeddinggemma-2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;270M&lt;/td&gt;
&lt;td&gt;768&lt;/td&gt;
&lt;td&gt;8k&lt;/td&gt;
&lt;td&gt;~540 MB&lt;/td&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;td&gt;EmbeddingGemma 2 text-only (disable vision/audio towers). 100+ languages + code; MRL truncates to 512/256/128; instruction prefixes for search vs document. Full multimodal load is 740M.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BAAI/bge-m3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;568M&lt;/td&gt;
&lt;td&gt;1024&lt;/td&gt;
&lt;td&gt;8k&lt;/td&gt;
&lt;td&gt;~1.1 GB&lt;/td&gt;
&lt;td&gt;CPU or GPU&lt;/td&gt;
&lt;td&gt;Strong multilingual dual-encoder; same width as Qwen3-0.6B, so a same-dim swap still needs a full re-embed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Qwen/Qwen3-Embedding-0.6B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.6B&lt;/td&gt;
&lt;td&gt;1024&lt;/td&gt;
&lt;td&gt;32k&lt;/td&gt;
&lt;td&gt;~1.2 GB&lt;/td&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;td&gt;This guide's default. Instruction-aware (search instruction on the query side); comfortable on CPU. Local provider auto-applies the instruction; OpenAI-compatible servers need &lt;code&gt;HINDSIGHT_API_EMBEDDINGS_QUERY_PREFIX&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Qwen/Qwen3-Embedding-4B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4B&lt;/td&gt;
&lt;td&gt;2560&lt;/td&gt;
&lt;td&gt;32k&lt;/td&gt;
&lt;td&gt;~8 GB FP16 / ~2.5 GB quant&lt;/td&gt;
&lt;td&gt;GPU&lt;/td&gt;
&lt;td&gt;Qwen3 quality tier; locks a 2560-dim bank. Not viable when the conversational GPU is already saturated.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;How the model is run is the other half of the choice. Hindsight's embedding providers, from simplest to most moving parts:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;How it runs&lt;/th&gt;
&lt;th&gt;Best when&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;local&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;In-process SentenceTransformers; no sidecar&lt;/td&gt;
&lt;td&gt;Single Hindsight host, CPU embedder&lt;/td&gt;
&lt;td&gt;Default path. Set &lt;code&gt;HINDSIGHT_API_EMBEDDINGS_LOCAL_MODEL&lt;/code&gt;. No llama.cpp, Ollama, or TEI.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;onnx&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;In-process ONNX Runtime&lt;/td&gt;
&lt;td&gt;Lighter CPU than Torch; you already have an ONNX export&lt;/td&gt;
&lt;td&gt;Needs &lt;code&gt;hindsight-api-slim[local-onnx]&lt;/code&gt; and &lt;code&gt;HINDSIGHT_API_EMBEDDINGS_ONNX_*&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;openai&lt;/code&gt; / &lt;code&gt;tei&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;External OpenAI-compatible or HF TEI endpoint&lt;/td&gt;
&lt;td&gt;Embeddings off the API process; shared GPU/CPU host&lt;/td&gt;
&lt;td&gt;What Serve and Wire Local Models configures for Ollama / llama.cpp / TEI. Only worth the sidecar if you want isolation or a different machine.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Qwen3-Embedding-0.6B does not need an external embeddings service. On a CPU Hindsight host it loads inside the API process with the &lt;code&gt;local&lt;/code&gt; provider — Hugging Face downloads the weights on first start, and the provider applies the model's search instruction itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Compose environment (or equivalent .env keys)&lt;/span&gt;
&lt;span class="na"&gt;HINDSIGHT_API_EMBEDDINGS_PROVIDER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local&lt;/span&gt;
&lt;span class="na"&gt;HINDSIGHT_API_EMBEDDINGS_LOCAL_MODEL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Qwen/Qwen3-Embedding-0.6B&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use an external OpenAI-compatible endpoint only when you want the embedder on another machine or off the API process. Ollama is one such host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Embeddings: Qwen3-Embedding-0.6B on Ollama&lt;/span&gt;
&lt;span class="na"&gt;HINDSIGHT_API_EMBEDDINGS_PROVIDER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openai&lt;/span&gt;
&lt;span class="na"&gt;HINDSIGHT_API_EMBEDDINGS_OPENAI_BASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://192.168.1.123:11434/v1&lt;/span&gt;
&lt;span class="na"&gt;HINDSIGHT_API_EMBEDDINGS_OPENAI_MODEL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;qwen3-embedding:0.6b-q8_0&lt;/span&gt;
&lt;span class="na"&gt;HINDSIGHT_API_EMBEDDINGS_OPENAI_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dummy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the host, port, and tag with whatever &lt;code&gt;ollama list&lt;/code&gt; shows on your inference machine.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;HINDSIGHT_API_EMBEDDINGS_QUERY_PREFIX&lt;/code&gt; depends on the provider:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;&lt;code&gt;HINDSIGHT_API_EMBEDDINGS_QUERY_PREFIX&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;HINDSIGHT_API_EMBEDDINGS_PASSAGE_PREFIX&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;local&lt;/code&gt; (&lt;code&gt;Qwen/Qwen3-Embedding-0.6B&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Leave unset / empty — the in-process provider applies Qwen's search instruction itself&lt;/td&gt;
&lt;td&gt;Leave unset / empty&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;External Ollama / llama.cpp / OpenAI-compatible&lt;/td&gt;
&lt;td&gt;Official Qwen3 retrieval instruction (real newline before &lt;code&gt;Query:&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Leave empty — stored memories stay raw text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server already wraps inputs in Qwen's template&lt;/td&gt;
&lt;td&gt;Leave both empty — otherwise the instruction is applied twice&lt;/td&gt;
&lt;td&gt;Leave empty&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For the external path, set:&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;# Real newline before Query:. Bash $'...' is the reliable way to pass it.&lt;/span&gt;
&lt;span class="nv"&gt;HINDSIGHT_API_EMBEDDINGS_QUERY_PREFIX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;$'Instruct: Given a web search query, retrieve relevant passages that answer the query&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;Query:'&lt;/span&gt;
&lt;span class="nv"&gt;HINDSIGHT_API_EMBEDDINGS_PASSAGE_PREFIX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Compose YAML the same string needs an explicit &lt;code&gt;\n&lt;/code&gt; (or a literal multiline value). Set the prefixes before the first retain: changing them later means old vectors and new queries no longer sit in the same space.&lt;/p&gt;

&lt;p&gt;Two properties decide more than the leaderboard position. &lt;strong&gt;Instruction sensitivity&lt;/strong&gt;: Qwen3 embeddings apply a search instruction on the query side; without the matching prefix on an OpenAI-compatible server, the stored side and the query side stop sitting in the same space. &lt;strong&gt;Dimension lock&lt;/strong&gt;: because the width is read once at startup, moving from 0.6B to 4B later is a bank migration, not a config change — and even a same-dimension swap (BGE-M3 ↔ Qwen3-0.6B, or EmbeddingGemma 2 ↔ nomic at 768) changes the vector space, so bank vectors are not recomputed automatically. Plan a fresh bank or a manual re-embed/reindex; see Export, import, and migrating the memory bank. After leaving BGE-small, probe &lt;code&gt;recall_min_scores&lt;/code&gt; against the new score distribution — thresholds calibrated on BGE-small can be too tight for Qwen3-0.6B and starve the reranker of candidates. For benchmarks and GGUF/Ollama background on the Qwen3 family, see &lt;a href="https://www.glukhov.org/rag/embeddings/qwen3-embedding-qwen3-reranker-on-ollama/" rel="noopener noreferrer"&gt;Qwen3 Embedding and Reranker models on Ollama&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reranking models
&lt;/h3&gt;

&lt;p&gt;Embeddings recall a candidate set; the reranker scores each query/passage pair and re-orders it before Hindsight injects it. With the &lt;code&gt;local&lt;/code&gt; provider, Hindsight loads a SentenceTransformers cross-encoder (or a generative reranker where supported). With an external Cohere-compatible endpoint, it posts the full Cohere-style body to &lt;code&gt;HINDSIGHT_API_RERANKER_COHERE_BASE_URL&lt;/code&gt;. The reranker is optional, but once the embedder is fixed it is usually the largest recall-quality lever — and, on CPU, often most of the recall wall-clock. Rows are sorted from lightest to heaviest.&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;Params&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Context&lt;/th&gt;
&lt;th&gt;Runs on&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cross-encoder/ms-marco-MiniLM-L-6-v2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;23M&lt;/td&gt;
&lt;td&gt;Cross-encoder&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;td&gt;Hindsight default; ships in &lt;code&gt;hindsight:latest&lt;/code&gt;. Sub-second for ~20 candidates. Keep as named fallback if a heavier BGE model is too slow.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cross-encoder/ms-marco-MiniLM-L-12-v2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~33M&lt;/td&gt;
&lt;td&gt;Cross-encoder&lt;/td&gt;
&lt;td&gt;512&lt;/td&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;td&gt;Same family, higher accuracy; still English-leaning MS MARCO.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cross-encoder/mmarco-mMiniLMv2-L12-H384-v1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~33M&lt;/td&gt;
&lt;td&gt;Cross-encoder&lt;/td&gt;
&lt;td&gt;512&lt;/td&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;td&gt;Hindsight-documented multilingual MiniLM reranker.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BAAI/bge-reranker-base&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;278M&lt;/td&gt;
&lt;td&gt;Cross-encoder&lt;/td&gt;
&lt;td&gt;512&lt;/td&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;td&gt;Moderate upgrade over MiniLM; en/zh. Discussed often; skipped when v2-m3 already fits the latency budget.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BAAI/bge-reranker-v2-m3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;568M&lt;/td&gt;
&lt;td&gt;Cross-encoder&lt;/td&gt;
&lt;td&gt;8k&lt;/td&gt;
&lt;td&gt;CPU or GPU&lt;/td&gt;
&lt;td&gt;Practical multilingual default on CPU. On a ~500-memory bank, ~2.6–2.8 s per recall at a 20-candidate floor (~140 ms/candidate; ~93–99% of recall time). Candidate caps 20/40/80; lowering the floor from 20 → 10 roughly halves rerank cost.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Qwen/Qwen3-Reranker-0.6B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.6B&lt;/td&gt;
&lt;td&gt;Generative (yes-token)&lt;/td&gt;
&lt;td&gt;32k&lt;/td&gt;
&lt;td&gt;GPU preferred&lt;/td&gt;
&lt;td&gt;Instruction-aware, long context. Not a drop-in cross-encoder: needs recent SentenceTransformers generative-reranker support, and llama.cpp &lt;code&gt;/rerank&lt;/code&gt; scoring has known wrong-score bugs (&lt;a href="https://github.com/vectorize-io/hindsight-benchmarks/issues/11" rel="noopener noreferrer"&gt;hindsight-benchmarks#11&lt;/a&gt;). Validate before adopting.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BAAI/bge-reranker-v2-gemma&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2.5B&lt;/td&gt;
&lt;td&gt;Cross-encoder&lt;/td&gt;
&lt;td&gt;8k&lt;/td&gt;
&lt;td&gt;GPU preferred&lt;/td&gt;
&lt;td&gt;Strong multilingual quality; excessive for a few-hundred-memory personal bank when the GPU is already full.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Qwen/Qwen3-Reranker-4B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4B&lt;/td&gt;
&lt;td&gt;Generative&lt;/td&gt;
&lt;td&gt;32k&lt;/td&gt;
&lt;td&gt;GPU&lt;/td&gt;
&lt;td&gt;Higher-quality Qwen candidate; not viable without dedicated VRAM.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Qwen/Qwen3-Reranker-8B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8B&lt;/td&gt;
&lt;td&gt;Generative&lt;/td&gt;
&lt;td&gt;32k&lt;/td&gt;
&lt;td&gt;GPU&lt;/td&gt;
&lt;td&gt;Family top tier; same generative-scoring caveats, larger still.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Cloud API rerankers (Cohere, ZeroEntropy, Alibaba &lt;code&gt;qwen3-rerank&lt;/code&gt;, TypeSafe, and similar) work in Hindsight but send memory text off-box. For a local-only cross-domain layout they are out of scope.&lt;/p&gt;

&lt;p&gt;Two operational constraints dominate the pick. &lt;strong&gt;VRAM&lt;/strong&gt;: if the conversational model already saturates the GPU (for example a Qwen3.8-27B Q5 on a 16 GB card with no headroom), every "GPU preferred" row is off the table and the live choice collapses to MiniLM or BGE-v2-m3 on CPU. &lt;strong&gt;Candidate count&lt;/strong&gt;: &lt;code&gt;HINDSIGHT_API_RERANKER_MAX_CANDIDATES&lt;/code&gt; (30 in the external-endpoint example below) bounds how many passages the model re-scores; each candidate is a full forward pass, so cut the floor before you jump a model tier. For Qwen3 via llama.cpp, the GGUF must keep the ranking head — an old &lt;code&gt;convert_hf_to_gguf.py&lt;/code&gt; that drops it scores every passage near zero — and even a correct conversion still needs a known-good/known-bad passage test until the generative scoring path is confirmed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generative LLM tiers
&lt;/h3&gt;

&lt;p&gt;Embedding and reranking models are not substitutes for the generative LLM used to extract and reason over memories. Hindsight supports a default LLM and operation-specific overrides, which can target the same llama.cpp server or independent endpoints.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hindsight operation&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Practical model tier&lt;/th&gt;
&lt;th&gt;Qwen examples&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Retain&lt;/td&gt;
&lt;td&gt;Extract facts, entities, decisions, and chronology&lt;/td&gt;
&lt;td&gt;4B minimum for tests; 9B preferred&lt;/td&gt;
&lt;td&gt;Qwen3.5-4B / Qwen3.5-9B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recall&lt;/td&gt;
&lt;td&gt;Semantic, keyword, graph and temporal retrieval&lt;/td&gt;
&lt;td&gt;No generative LLM required&lt;/td&gt;
&lt;td&gt;Qwen3-Embedding-0.6B + reranker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reflect&lt;/td&gt;
&lt;td&gt;Answer questions by synthesizing retrieved memories&lt;/td&gt;
&lt;td&gt;9B to 27B+&lt;/td&gt;
&lt;td&gt;Qwen3.5-9B / Qwen3.8-27B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Consolidation&lt;/td&gt;
&lt;td&gt;Reconcile and compress related facts&lt;/td&gt;
&lt;td&gt;9B preferred&lt;/td&gt;
&lt;td&gt;Qwen3.5-9B / Qwen3.8-27B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mental model refresh&lt;/td&gt;
&lt;td&gt;Update longer-lived synthesized understanding&lt;/td&gt;
&lt;td&gt;9B to 35B&lt;/td&gt;
&lt;td&gt;Qwen3.5-9B / Qwen3.6-35B-A3B&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are operational recommendations, not official Hindsight minimum parameter counts. A small model may extract a port number correctly but miss that an earlier architectural decision has been superseded. Structured-output reliability and faithful handling of negation matter more than nominal parameter count alone. Hindsight's own temperature defaults are worth leaving alone — &lt;code&gt;0.1&lt;/code&gt; for retain, &lt;code&gt;0.0&lt;/code&gt; for consolidation, &lt;code&gt;0.9&lt;/code&gt; for reflect — and for retain and consolidation, the non-thinking alias is worth serving if your llama.cpp build has one, because the small Qwen3.5 checkpoints spend many tokens in thinking mode.&lt;/p&gt;

&lt;p&gt;For a deployment with an existing Qwen3.8-27B llama.cpp endpoint, start by sharing it across generative Hindsight tasks with low concurrency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HINDSIGHT_API_LLM_PROVIDER=openai
HINDSIGHT_API_LLM_BASE_URL=http://WORKSTATION_IP:8080/v1
HINDSIGHT_API_LLM_MODEL=LocalMegaLLM
HINDSIGHT_API_LLM_API_KEY=dummy
HINDSIGHT_API_LLM_MAX_CONCURRENT=2
HINDSIGHT_API_RETAIN_LLM_MAX_CONCURRENT=1
HINDSIGHT_API_CONSOLIDATION_LLM_MAX_CONCURRENT=1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Later, route routine extraction to an independent 9B model and reserve the 27B for difficult reflection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HINDSIGHT_API_RETAIN_LLM_PROVIDER=openai
HINDSIGHT_API_RETAIN_LLM_BASE_URL=http://MEMORY_LLM_HOST:8083/v1
HINDSIGHT_API_RETAIN_LLM_MODEL=MemoryLLM
HINDSIGHT_API_RETAIN_LLM_API_KEY=dummy

HINDSIGHT_API_REFLECT_LLM_PROVIDER=openai
HINDSIGHT_API_REFLECT_LLM_BASE_URL=http://WORKSTATION_IP:8080/v1
HINDSIGHT_API_REFLECT_LLM_MODEL=LocalMegaLLM
HINDSIGHT_API_REFLECT_LLM_API_KEY=dummy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The smaller 4B model is economical but needs testing; 9B is a sensible quality/performance compromise. If retain and consolidation share a small model, lower &lt;code&gt;HINDSIGHT_API_CONSOLIDATION_LLM_BATCH_SIZE&lt;/code&gt; to &lt;code&gt;2&lt;/code&gt; — the default batch of 8 is a long prompt for a small context window. Qwen3.6-35B-A3B is attractive for a separate well-provisioned inference server (it is a mixture-of-experts model with about 3 billion active parameters, so budget it as a small active model), while a 27B dense model is usually unnecessary for every routine retain event. Limiting background concurrency protects the Hermes conversational model from memory-maintenance bursts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Serve and Wire Local Models
&lt;/h2&gt;

&lt;p&gt;If the in-process &lt;code&gt;local&lt;/code&gt; embedding and reranker settings in Choose Models are enough, you can skip the external Ollama and llama.cpp subsections and jump to wiring — or leave Compose on the &lt;code&gt;local&lt;/code&gt; defaults shown there. External endpoints matter when the embedder or reranker should live on another host.&lt;/p&gt;

&lt;h3&gt;
  
  
  External embeddings: Ollama or llama.cpp
&lt;/h3&gt;

&lt;p&gt;External embeddings are for when you want Qwen3-Embedding-0.6B (or another model) on a separate host. Two practical options are Ollama, which handles model acquisition and serving with little configuration, and llama.cpp, which gives precise control over GGUF quantization, CPU threads, device placement, and batching. Model background and benchmarks for both Qwen3 models are in &lt;a href="https://www.glukhov.org/rag/embeddings/qwen3-embedding-qwen3-reranker-on-ollama/" rel="noopener noreferrer"&gt;Qwen3 Embedding and Reranker models on Ollama&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Qwen3-Embedding-0.6B returns 1024-dimensional vectors, and Hindsight reads that width on startup and sizes the database to match. A bank created with the bundled 384-dimensional BGE model cannot take this model — start from an empty database or volume, and decide the embedding model before the first retain. An embeddings provider switch later requires re-embedding stored records even if both models use the same dimensionality.&lt;/p&gt;

&lt;h4&gt;
  
  
  Option 1: Ollama for Qwen3-Embedding-0.6B
&lt;/h4&gt;

&lt;p&gt;Install Ollama on the inference host using its &lt;a href="https://ollama.com/download/linux" rel="noopener noreferrer"&gt;official Linux installation documentation&lt;/a&gt;, then start the embedding model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull qwen3-embedding:0.6b
ollama serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For remote access, configure Ollama's &lt;code&gt;OLLAMA_HOST=0.0.0.0:11434&lt;/code&gt; in its service environment, restart the service, and firewall it to the Hindsight VM — the procedure is covered in &lt;a href="https://www.glukhov.org/llm-hosting/ollama/ollama-remote-access/" rel="noopener noreferrer"&gt;Ollama remote access&lt;/a&gt;. A daemon bound only to &lt;code&gt;127.0.0.1&lt;/code&gt; is invisible to the Hindsight container. First check the model name with &lt;code&gt;ollama list&lt;/code&gt;, and test the standard embedding endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://EMBED_HOST:11434/api/embed &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"model":"qwen3-embedding:0.6b","input":"A test memory"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hindsight's OpenAI embeddings provider instead calls Ollama's OpenAI-compatible &lt;code&gt;/v1/embeddings&lt;/code&gt; endpoint. Verify that endpoint works with your Ollama version before configuring Hindsight. &lt;strong&gt;Ollama is straightforward for embeddings; do not assume its API offers a compatible reranking endpoint.&lt;/strong&gt; For reranking, prefer the in-process &lt;code&gt;local&lt;/code&gt; BGE path below, or a separate llama.cpp / TEI endpoint after you have validated scores.&lt;/p&gt;

&lt;h4&gt;
  
  
  Option 2: llama.cpp for Qwen3-Embedding-0.6B
&lt;/h4&gt;

&lt;p&gt;Download a verified &lt;a href="https://huggingface.co/Qwen/Qwen3-Embedding-0.6B" rel="noopener noreferrer"&gt;Qwen3-Embedding-0.6B GGUF&lt;/a&gt; conversion. Use the GGUF filename you actually downloaded:&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;#!/bin/sh&lt;/span&gt;
&lt;span class="c"&gt;# start-embedding.sh - CPU-only embedding server&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-eu&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/prj"&lt;/span&gt;
&lt;span class="nb"&gt;exec&lt;/span&gt; ./llama.cpp/llama-server &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--host&lt;/span&gt; 0.0.0.0 &lt;span class="nt"&gt;--port&lt;/span&gt; 8081 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--model&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/ggufs/Qwen3-Embedding-0.6B-Q8_0.gguf"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--alias&lt;/span&gt; qwen3-embedding-0.6b &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--embedding&lt;/span&gt; &lt;span class="nt"&gt;--pooling&lt;/span&gt; last &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--ctx-size&lt;/span&gt; 4096 &lt;span class="nt"&gt;--parallel&lt;/span&gt; 1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--threads&lt;/span&gt; 4 &lt;span class="nt"&gt;--threads-batch&lt;/span&gt; 4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--batch-size&lt;/span&gt; 512 &lt;span class="nt"&gt;--ubatch-size&lt;/span&gt; 256 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--n-gpu-layers&lt;/span&gt; 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Qwen3 embeddings use last-token pooling, unlike the CLS pooling used by some BGE models. A CPU-only llama.cpp build is preferable if you want absolutely no CUDA initialization on a workstation whose GPU is occupied by a large conversational model. Do not enable &lt;code&gt;--reranking&lt;/code&gt; on this embedding server: that combination is a known way to get a vector of zeros.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://EMBED_HOST:8081/v1/embeddings &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"model":"qwen3-embedding-0.6b","input":"A test memory"}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="s1"&gt;'.data[0].embedding | length'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A full-dimension Qwen3-Embedding-0.6B output should contain 1024 floats. Check that the GGUF conversion and your Hindsight query/document instruction handling are compatible; instruction-sensitive embedding models can lose accuracy when prompted incorrectly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reranker: local BGE or Qwen3 via llama.cpp
&lt;/h3&gt;

&lt;p&gt;Reranking is different from embeddings: the model scores each query/document pair and returns a relevance score. On a CPU-only Hindsight host — or whenever the conversational GPU has no VRAM headroom — the practical path is Hindsight's &lt;code&gt;local&lt;/code&gt; provider with &lt;code&gt;BAAI/bge-reranker-v2-m3&lt;/code&gt; (or the lighter MiniLM default). No sidecar is required:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HINDSIGHT_API_RERANKER_PROVIDER=local
HINDSIGHT_API_RERANKER_LOCAL_MODEL=BAAI/bge-reranker-v2-m3
HINDSIGHT_API_RERANKER_MAX_CANDIDATES=20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Qwen3-Reranker-0.6B is a generative yes-token scorer, not a classic cross-encoder. It can be served through llama.cpp with a GGUF conversion designed for ranking, but treat that path as experimental until you confirm scores on a known-good/known-bad pair: an old &lt;code&gt;convert_hf_to_gguf.py&lt;/code&gt; that drops the ranking head scores every passage near zero, and llama.cpp's native &lt;code&gt;/rerank&lt;/code&gt; path has known wrong-score reports for this family.&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;#!/bin/sh&lt;/span&gt;
&lt;span class="c"&gt;# start-reranker.sh - GPU or CPU as selected by --n-gpu-layers&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-eu&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/prj"&lt;/span&gt;
&lt;span class="nb"&gt;exec&lt;/span&gt; ./llama.cpp/llama-server &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--host&lt;/span&gt; 0.0.0.0 &lt;span class="nt"&gt;--port&lt;/span&gt; 8082 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--model&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/ggufs/Qwen3-Reranker-0.6B-Q8_0.gguf"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--alias&lt;/span&gt; qwen3-reranker-0.6b &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--embedding&lt;/span&gt; &lt;span class="nt"&gt;--reranking&lt;/span&gt; &lt;span class="nt"&gt;--pooling&lt;/span&gt; rank &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--ctx-size&lt;/span&gt; 4096 &lt;span class="nt"&gt;--parallel&lt;/span&gt; 1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--batch-size&lt;/span&gt; 512 &lt;span class="nt"&gt;--ubatch-size&lt;/span&gt; 256 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--n-gpu-layers&lt;/span&gt; 99
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set &lt;code&gt;--n-gpu-layers 0&lt;/code&gt; to run it on CPU. The GPU version needs additional VRAM for compute buffers and context, not just model weights; avoid assuming that the file size is the total GPU allocation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://RERANK_HOST:8082/v1/rerank &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"model":"qwen3-reranker-0.6b", "query":"PostgreSQL storage", "documents":["Hindsight stores memories in PostgreSQL.","A recipe for bread."], "top_n":2}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | jq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The PostgreSQL document should rank above the unrelated document; all-zero relevance scores mean the GGUF is the wrong conversion, and recall will look random even when the facts are stored. Check the exact &lt;code&gt;results[].index&lt;/code&gt; and &lt;code&gt;results[].relevance_score&lt;/code&gt; shape: Hindsight expects a Cohere-compatible rerank response, and llama.cpp compatibility can vary across builds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wire endpoints into Hindsight
&lt;/h3&gt;

&lt;p&gt;Add the following environment settings to the Hindsight Compose service, or store them in its &lt;code&gt;.env&lt;/code&gt; file. Pick one embeddings path — in-process &lt;code&gt;local&lt;/code&gt;, Ollama, or llama.cpp — never two at once.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Option A: in-process Qwen3 on the Hindsight host (CPU; no sidecar)
HINDSIGHT_API_EMBEDDINGS_PROVIDER=local
HINDSIGHT_API_EMBEDDINGS_LOCAL_MODEL=Qwen/Qwen3-Embedding-0.6B

# Option B: llama.cpp embedding service
# HINDSIGHT_API_EMBEDDINGS_PROVIDER=openai
# HINDSIGHT_API_EMBEDDINGS_OPENAI_BASE_URL=http://EMBED_HOST:8081/v1
# HINDSIGHT_API_EMBEDDINGS_OPENAI_MODEL=qwen3-embedding-0.6b
# HINDSIGHT_API_EMBEDDINGS_OPENAI_API_KEY=dummy
# HINDSIGHT_API_EMBEDDINGS_OPENAI_BATCH_SIZE=8
# HINDSIGHT_API_EMBEDDINGS_QUERY_PREFIX=$'Instruct: Given a web search query, retrieve relevant passages that answer the query\nQuery:'
# HINDSIGHT_API_EMBEDDINGS_PASSAGE_PREFIX=

# Option C: Ollama on another host
# HINDSIGHT_API_EMBEDDINGS_PROVIDER=openai
# HINDSIGHT_API_EMBEDDINGS_OPENAI_BASE_URL=http://192.168.1.40:11434/v1
# HINDSIGHT_API_EMBEDDINGS_OPENAI_MODEL=qwen3-embedding:0.6b-q8_0
# HINDSIGHT_API_EMBEDDINGS_OPENAI_API_KEY=dummy
# HINDSIGHT_API_EMBEDDINGS_QUERY_PREFIX=$'Instruct: Given a web search query, retrieve relevant passages that answer the query\nQuery:'
# HINDSIGHT_API_EMBEDDINGS_PASSAGE_PREFIX=

# Option: local BGE reranker (no sidecar; preferred on CPU)
HINDSIGHT_API_RERANKER_PROVIDER=local
HINDSIGHT_API_RERANKER_LOCAL_MODEL=BAAI/bge-reranker-v2-m3
HINDSIGHT_API_RERANKER_MAX_CANDIDATES=20

# Option: llama.cpp Cohere-compatible rerank endpoint (validate Qwen3 scores first)
# HINDSIGHT_API_RERANKER_PROVIDER=cohere
# HINDSIGHT_API_RERANKER_COHERE_BASE_URL=http://RERANK_HOST:8082/v1/rerank
# HINDSIGHT_API_RERANKER_COHERE_MODEL=qwen3-reranker-0.6b
# HINDSIGHT_API_RERANKER_COHERE_API_KEY=dummy
# HINDSIGHT_API_RERANKER_MAX_CANDIDATES=30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The variable names are provider-specific, and a near-miss is silent: &lt;code&gt;HINDSIGHT_API_EMBEDDINGS_BASE_URL&lt;/code&gt; is the wrong key, and Hindsight quietly aims at OpenAI's own embedding endpoint instead of your local server. Use &lt;code&gt;HINDSIGHT_API_EMBEDDINGS_OPENAI_BASE_URL&lt;/code&gt;. With the Cohere-compatible reranker path, Hindsight posts the full Cohere-style body to &lt;code&gt;HINDSIGHT_API_RERANKER_COHERE_BASE_URL&lt;/code&gt;; if the container log shows a 404 for &lt;code&gt;/v1/rerank/rerank&lt;/code&gt;, your build appends &lt;code&gt;/rerank&lt;/code&gt; on its own and the value should stop at &lt;code&gt;/v1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For Options B and C, use the &lt;code&gt;HINDSIGHT_API_EMBEDDINGS_QUERY_PREFIX&lt;/code&gt; value from Embedding models (Option A leaves it unset). Test query-to-passage retrieval on your actual memories after the first retain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Control What Gets Remembered
&lt;/h2&gt;

&lt;p&gt;With the stack running, the next failure mode is usually the write path: Hermes defaults to &lt;code&gt;auto_retain: true&lt;/code&gt;, so every conversation turn can be extracted into the bank. Incorrect guesses, discarded plans, and exploratory statements become searchable facts, and later recall treats them as evidence — the loop described in &lt;a href="https://www.glukhov.org/ai-systems/memory/self-reinforcing-memory-loops/" rel="noopener noreferrer"&gt;self-reinforcing memory loops&lt;/a&gt;. Capture policy for Hindsight among other providers is summarized in &lt;a href="https://www.glukhov.org/ai-systems/memory/agent-memory-providers/" rel="noopener noreferrer"&gt;agent memory providers compared&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Capture modes that exist today
&lt;/h3&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;Hermes / Hindsight settings&lt;/th&gt;
&lt;th&gt;What you get&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Continuous&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;auto_retain: true&lt;/code&gt;, &lt;code&gt;retain_every_n_turns: 1&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Every turn is retained and extracted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Batched auto&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;auto_retain: true&lt;/code&gt;, raise &lt;code&gt;retain_every_n_turns&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Fewer, larger retain payloads — still automatic, not a review queue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explicit-only&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;auto_retain: false&lt;/code&gt; + tool &lt;code&gt;hindsight_retain&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Storage only when the agent calls the tool (for example after you say "remember this")&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Session synthesis&lt;/td&gt;
&lt;td&gt;End-of-session &lt;code&gt;hindsight_retain&lt;/code&gt; of a summary, or bank reflect / consolidation&lt;/td&gt;
&lt;td&gt;A curated write if you or the agent discipline the call — not a built-in flush-for-approval&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Setting &lt;code&gt;auto_retain&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt; disables automatic storage. It does &lt;strong&gt;not&lt;/strong&gt; add a human approval dialog before each write. The agent can still call &lt;code&gt;hindsight_retain&lt;/code&gt; whenever its tools allow; treat explicit retain as deliberate capture, not a pending queue.&lt;/p&gt;

&lt;h3&gt;
  
  
  The approval-queue gap
&lt;/h3&gt;

&lt;p&gt;A common wish is a pending queue of candidate memories that you review and approve before they enter the bank. Hindsight under Hermes does not provide that contract today. &lt;a href="https://www.glukhov.org/ai-systems/hermes/mnemosyne-memory/" rel="noopener noreferrer"&gt;Mnemosyne for Hermes&lt;/a&gt; implements provider-specific staged writes in some versions; that path is not a shared Hermes standard and does not apply to the Hindsight plugin. Until an upstream approval flow exists, the workable pattern is &lt;code&gt;auto_retain: false&lt;/code&gt;, deliberate &lt;code&gt;hindsight_retain&lt;/code&gt;, and inspection or deletion in the Hindsight control-plane UI before the next session relies on a bad fact. There is no missing &lt;code&gt;write_approval&lt;/code&gt; key for Hindsight to hunt for in &lt;code&gt;config.json&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bank steering (missions, labels, directives, mental models)
&lt;/h3&gt;

&lt;p&gt;People often call the next layer "finetuning." In Hindsight it is mostly &lt;strong&gt;bank configuration&lt;/strong&gt;, not Hermes &lt;code&gt;config.json&lt;/code&gt; alone: missions and extraction settings steer what retain keeps; labels structure entities; directives and mental models shape later synthesis.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Control&lt;/th&gt;
&lt;th&gt;Where it lives&lt;/th&gt;
&lt;th&gt;How it helps&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Retain mission (&lt;code&gt;retain_mission&lt;/code&gt; / Hermes &lt;code&gt;bank_retain_mission&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Bank config&lt;/td&gt;
&lt;td&gt;Plain-language "pay attention to X, ignore Y" injected into extraction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extraction mode (&lt;code&gt;concise&lt;/code&gt;, &lt;code&gt;verbose&lt;/code&gt;, &lt;code&gt;custom&lt;/code&gt;, …)&lt;/td&gt;
&lt;td&gt;Bank config&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;concise&lt;/code&gt; (default) keeps long-term-worthy facts; &lt;code&gt;custom&lt;/code&gt; replaces built-in rules via &lt;code&gt;retain_custom_instructions&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entity labels&lt;/td&gt;
&lt;td&gt;Bank config&lt;/td&gt;
&lt;td&gt;Controlled &lt;code&gt;key:value&lt;/code&gt; vocabulary at retain time → graph links and optional tags; improves retrieval structure, does not by itself reject wrong statements&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Directives ("hard rules")&lt;/td&gt;
&lt;td&gt;Bank directives API&lt;/td&gt;
&lt;td&gt;Explicit instructions enforced during &lt;strong&gt;reflect&lt;/strong&gt; (tag-scoped); constrain synthesis, not a write-approval gate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mental models&lt;/td&gt;
&lt;td&gt;Bank mental-model API&lt;/td&gt;
&lt;td&gt;Standing answers to recurring questions, refreshed via reflect from matching memories; derived, so bad source facts still poison them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reflect / observations mission&lt;/td&gt;
&lt;td&gt;Bank config&lt;/td&gt;
&lt;td&gt;Steers synthesis and consolidation, not the raw retain firehose&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Missions, concise extraction, and labels reduce noise and improve retrieval. They do &lt;strong&gt;not&lt;/strong&gt; replace turning off auto-retain when accuracy matters more than completeness. Prefer a short retain mission that names durable project decisions and ignores chat logistics; add entity labels only when you have a real taxonomy; add mental models for two or three recurring questions after the bank already holds clean facts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recommended hygiene profile
&lt;/h3&gt;

&lt;p&gt;A concrete starting point for a personal Hermes bank:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"local_external"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"api_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://HINDSIGHT_VM_IP:8888"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bank_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hermes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"memory_mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hybrid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"auto_recall"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"recall_sync"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"auto_retain"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"retain_async"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"recall_prefetch_method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"recall"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the bank, set a retain mission and leave extraction in &lt;code&gt;concise&lt;/code&gt; mode (via the control plane or Banks API). After an explicit retain, open the UI, confirm the observation, and delete mistakes before consolidation or mental-model refresh runs. Leave &lt;code&gt;recall_prefetch_method&lt;/code&gt; at &lt;code&gt;recall&lt;/code&gt; — &lt;code&gt;reflect&lt;/code&gt; spends a chat-model call before every turn and injects synthesis rather than underlying facts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect Hermes Agent to Hindsight
&lt;/h2&gt;

&lt;p&gt;On the &lt;strong&gt;Hermes VM&lt;/strong&gt;, install the catalog plugin, then run the setup wizard. Select Hindsight and the &lt;code&gt;local_external&lt;/code&gt; mode; the plugin does not need to start embedded PostgreSQL or run the Hindsight model stack locally, and &lt;code&gt;local_embedded&lt;/code&gt; would start a second Hindsight daemon beside the container you just ran.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hermes plugins &lt;span class="nb"&gt;install &lt;/span&gt;hindsight
hermes memory setup hindsight
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There will be Interesting set of options of agent role using hindsight&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hermes memory status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The external Hindsight connection lives in &lt;code&gt;~/.hermes/hindsight/config.json&lt;/code&gt;. An illustrative configuration that keeps continuous capture (Hermes defaults) is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"local_external"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"api_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://HINDSIGHT_VM_IP:8888"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bank_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hermes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"memory_mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hybrid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"auto_recall"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"recall_sync"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"auto_retain"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"retain_async"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"retain_every_n_turns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For explicit-only retain and bank steering, use the hygiene profile in Control What Gets Remembered instead of leaving &lt;code&gt;auto_retain&lt;/code&gt; on. &lt;code&gt;recall_sync: false&lt;/code&gt; prioritizes interactive responsiveness but may inject recalled context on the &lt;strong&gt;following&lt;/strong&gt; turn. Use synchronous recall when the current request must incorporate the latest memories before the model starts answering. &lt;code&gt;retain_async: true&lt;/code&gt; avoids blocking ordinary responses while memories are processed.&lt;/p&gt;

&lt;p&gt;The plugin's recall default is observations only; raw world and experience facts stay in the bank and out of the prompt until consolidation has written an observation, or until you widen &lt;code&gt;recall_types&lt;/code&gt; to &lt;code&gt;observation,world,experience&lt;/code&gt;. Widening it for the first week and watching the control plane, then narrowing, is a reasonable order.&lt;/p&gt;

&lt;p&gt;Built-in Hermes memory stays active next to the provider; keeping &lt;code&gt;MEMORY.md&lt;/code&gt; and &lt;code&gt;USER.md&lt;/code&gt; on while you learn what Hindsight adds is fine, and the memory-system article is the place to decide whether the flat files should stay.&lt;/p&gt;

&lt;p&gt;Restart the gateway if it is already running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hermes gateway restart
hermes memory status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The status should identify Hindsight as the active provider. For a wider command reference, including Hermes' TUI and plugin commands, see the &lt;a href="https://www.glukhov.org/ai-systems/hermes/hermes-agent-cli-cheatsheet/" rel="noopener noreferrer"&gt;Hermes Agent CLI cheatsheet&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Validate Memory, Not Just Installation
&lt;/h3&gt;

&lt;p&gt;Ask Hermes to remember a disposable fact about a test project, start another session, and ask for that fact again. Retention is asynchronous by default and extraction waits on the chat model, so give the main llama.cpp server a moment before judging. Then test a harder case: tell Hermes that an earlier decision has changed and ask which decision is current and why. Check Hindsight's UI and stored observations rather than assuming &lt;code&gt;memory status&lt;/code&gt; proves ingestion and recall are correct.&lt;/p&gt;

&lt;p&gt;If the answer comes back empty, inspect the control plane before changing models. A retain can succeed and still be invisible to recall while &lt;code&gt;recall_types&lt;/code&gt; is observations only and consolidation has not run — widening &lt;code&gt;recall_types&lt;/code&gt; is the first check. A connection refused in the embedding server log means the container cannot reach the embedding port, and all-zero reranker scores mean the GGUF is the wrong conversion.&lt;/p&gt;

&lt;p&gt;Write-path choices (&lt;code&gt;auto_retain&lt;/code&gt;, missions, approval gap) are covered in Control What Gets Remembered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting and Operational Notes
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Likely cause&lt;/th&gt;
&lt;th&gt;First check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hermes says provider unavailable&lt;/td&gt;
&lt;td&gt;Wrong &lt;code&gt;local_external&lt;/code&gt; URL or network rule&lt;/td&gt;
&lt;td&gt;&lt;code&gt;curl http://HINDSIGHT_VM_IP:8888/health&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker starts, PostgreSQL errors&lt;/td&gt;
&lt;td&gt;Missing extension, wrong credentials, or insufficient shared memory&lt;/td&gt;
&lt;td&gt;Container logs and &lt;code&gt;pg_extension&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Embeddings return 401 or 404&lt;/td&gt;
&lt;td&gt;Incorrect OpenAI-compatible URL, wrong env key name, or model alias mismatch&lt;/td&gt;
&lt;td&gt;Direct &lt;code&gt;/v1/embeddings&lt;/code&gt; test and variable names&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reranking fails&lt;/td&gt;
&lt;td&gt;Endpoint or response format mismatch, including doubled &lt;code&gt;/rerank&lt;/code&gt; path&lt;/td&gt;
&lt;td&gt;Direct &lt;code&gt;/v1/rerank&lt;/code&gt; test and container log&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recall returns nothing usable&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;recall_types&lt;/code&gt; limited to observations, or zero reranker scores&lt;/td&gt;
&lt;td&gt;Control plane, then &lt;code&gt;recall_types&lt;/code&gt;, then reranker conversion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Existing bank reports dimension mismatch&lt;/td&gt;
&lt;td&gt;Embedding model changed&lt;/td&gt;
&lt;td&gt;Rebuild vectors/reimport with a consistent model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hermes responses slow down&lt;/td&gt;
&lt;td&gt;Synchronous recall or shared LLM contention&lt;/td&gt;
&lt;td&gt;Turn off sync recall; cap retain concurrency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Irrelevant memories repeat&lt;/td&gt;
&lt;td&gt;Weak extraction, noisy retrieval, or insufficient consolidation&lt;/td&gt;
&lt;td&gt;Inspect observations and relevance scores&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A practical small deployment is 2-4 vCPUs and 4-6 GB RAM for the Hindsight API VM &lt;strong&gt;when PostgreSQL and all inference are external&lt;/strong&gt;. Disk allocation still depends on the chosen Docker image and retained image layers; allocate roughly 30-40 GB initially with room to expand. Size the PostgreSQL host based on actual memory volume, storage performance, and the other databases it serves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Back up and restore the Hindsight bank
&lt;/h2&gt;

&lt;p&gt;The memory bank is the only state that matters, and it lives in PostgreSQL. In Option B that is a database you administer; in Option A it is the embedded &lt;code&gt;pg0&lt;/code&gt; named volume inside the container. Back it up independently of the Hindsight container — recreating the container without first capturing the volume or the database discards the bank, and &lt;code&gt;hermes backup&lt;/code&gt; does not reach a bank running in a separate service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option B — external PostgreSQL.&lt;/strong&gt; Dump the &lt;code&gt;hindsight&lt;/code&gt; database in custom format, then restore it into a scratch database to verify the archive before you trust it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pg_dump &lt;span class="nt"&gt;-h&lt;/span&gt; POSTGRES_IP &lt;span class="nt"&gt;-U&lt;/span&gt; hindsight &lt;span class="nt"&gt;-Fc&lt;/span&gt; hindsight &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; hindsight-bank.dump

&lt;span class="c"&gt;# restore into a scratch database to prove the dump is usable&lt;/span&gt;
createdb &lt;span class="nt"&gt;-h&lt;/span&gt; POSTGRES_IP &lt;span class="nt"&gt;-U&lt;/span&gt; postgres hindsight-verify
pg_restore &lt;span class="nt"&gt;-h&lt;/span&gt; POSTGRES_IP &lt;span class="nt"&gt;-U&lt;/span&gt; postgres &lt;span class="nt"&gt;-d&lt;/span&gt; hindsight-verify hindsight-bank.dump
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm the pgvector extension and a sample recall exist in the restored copy before you delete the original dump target. Keep the dump on a different host than the Hindsight VM, and run this as a PostgreSQL admin (a role that can create databases) rather than the application role.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option A — all-in-one container.&lt;/strong&gt; The embedded database is a named volume, so stop the container and tar the volume through a throwaway image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker stop hindsight
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; hindsight-pg0:/data &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;:/backup alpine &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;tar &lt;/span&gt;czf /backup/hindsight-bank.tgz &lt;span class="nt"&gt;-C&lt;/span&gt; /data &lt;span class="nb"&gt;.&lt;/span&gt;
docker start hindsight
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mount point inside the tar container (&lt;code&gt;/data&lt;/code&gt;) is arbitrary; it simply aliases the named volume so &lt;code&gt;tar&lt;/code&gt; can read it. To restore, stop the container, clear the volume, and untar the archive back into it, or point the container at the external PostgreSQL restore from Option B.&lt;/p&gt;

&lt;p&gt;Take a snapshot at three moments rather than on a calendar schedule: before changing the embedding model, before a major Hindsight or PostgreSQL upgrade, and before a &lt;code&gt;reflect&lt;/code&gt; or consolidation pass that rewrites the bank. A backup you have restored into a scratch bank is worth more than one you have only written.&lt;/p&gt;

&lt;h2&gt;
  
  
  Export, import, and migrating the memory bank
&lt;/h2&gt;

&lt;p&gt;Backup and restore protect the bank in place. Export/import moves it between Hindsight instances, and that is what a model change actually forces you to do.&lt;/p&gt;

&lt;p&gt;Changing the embedding model is not a configuration edit. The vectors in the bank were computed with the old model, and a different model — for example the bundled 384-dimensional BGE embedder versus 1024-dimensional Qwen3-Embedding-0.6B — writes into a different space and usually a different width. You cannot re-point the existing instance at the new embedder and keep the old vectors; the dimensions must be recalculated by re-embedding every memory with the new embedding model.&lt;/p&gt;

&lt;p&gt;Because the embedder and its configuration are set per Hindsight instance, the clean path is to stand up a fresh instance with the new embedding configuration and migrate the bank into it, rather than rebuilding vectors inside the one that is already serving recall. The Hindsight API can move the bank too, but for a one-off change export/import is the easier and more inspectable route:&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;# export the bank from the old instance&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;hindsight &lt;span class="se"&gt;\&lt;/span&gt;
  hindsight-admin export-bank &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--bank&lt;/span&gt; hermes2 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--output&lt;/span&gt; /tmp/hermes2.zip

docker &lt;span class="nb"&gt;cp &lt;/span&gt;hindsight:/tmp/hermes2.zip ./hermes2.zip

&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-lh&lt;/span&gt; hermes2.zip
unzip &lt;span class="nt"&gt;-t&lt;/span&gt; hermes2.zip

&lt;span class="c"&gt;# import the bank into the new instance&lt;/span&gt;
docker &lt;span class="nb"&gt;cp&lt;/span&gt; ./hermes2.zip hindsight-q06:/tmp/hermes2.zip

docker &lt;span class="nb"&gt;exec &lt;/span&gt;hindsight-q06 &lt;span class="se"&gt;\&lt;/span&gt;
  hindsight-admin import-bank &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--archive&lt;/span&gt; /tmp/hermes2.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;unzip -t&lt;/code&gt; verifies the archive before you ship it to the new instance. After the import, re-point Hermes at the new instance's &lt;code&gt;api_url&lt;/code&gt; in &lt;code&gt;~/.hermes/hindsight/config.json&lt;/code&gt;, restart the gateway, and run the memory validation from earlier: a retained fact should recall through the new embedder. Leave the old bank and its vectors in the old instance until the new one behaves, so you still have a working fallback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitor, prune, and keep the bank stable
&lt;/h2&gt;

&lt;p&gt;With the bank running, the day-to-day job is watching what the extraction model has decided to store and keeping derived state in check. Use the control plane UI (&lt;code&gt;hindsight-embed -p hermes ui start&lt;/code&gt;) to inspect observations, relevance scores, and consolidation output rather than assuming &lt;code&gt;memory status&lt;/code&gt; proves ingestion and recall are correct. Duplicated or stale memories are usually a weak extraction or an under-run consolidation, so fix the extraction tier or consolidation cadence before widening retrieval.&lt;/p&gt;

&lt;p&gt;Two maintenance traps are worth testing before you rely on the bank. First, deleting an observation does not guarantee every derived representation disappears with it — a &lt;code&gt;reflect&lt;/code&gt; fact or graph edge built from it can survive the delete, so verify deletion end to end, the same provenance concern covered in &lt;a href="https://www.glukhov.org/ai-systems/memory/self-reinforcing-memory-loops/" rel="noopener noreferrer"&gt;self-reinforcing memory loops&lt;/a&gt;. Second, keep &lt;code&gt;HINDSIGHT_API_WORKER_ID&lt;/code&gt; stable across recreations and pin a specific image tag: a task that was mid-flight when the container died stays stuck under the old worker id, and a moving tag is a poor match for a database you intend to keep.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/vectorize-io/hindsight" rel="noopener noreferrer"&gt;Hindsight source and documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/vectorize-io/hindsight/blob/main/skills/hindsight-docs/references/developer/installation.md" rel="noopener noreferrer"&gt;Official Hindsight installation guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/vectorize-io/hindsight/blob/main/skills/hindsight-docs/references/developer/configuration.md" rel="noopener noreferrer"&gt;Hindsight configuration reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/vectorize-io/hindsight/blob/main/hindsight-docs/docs-integrations/hermes.md" rel="noopener noreferrer"&gt;Hindsight Hermes integration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hindsight.vectorize.io/developer/models" rel="noopener noreferrer"&gt;Hindsight inference models and reranking providers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pgvector/pgvector" rel="noopener noreferrer"&gt;pgvector&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md" rel="noopener noreferrer"&gt;llama.cpp server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>hermes</category>
      <category>selfhosting</category>
      <category>llamacpp</category>
      <category>ollama</category>
    </item>
    <item>
      <title>Upgrading to Go 1.27: What Breaks, What to Adopt First</title>
      <dc:creator>Rost</dc:creator>
      <pubDate>Fri, 16 Oct 2026 09:33:44 +0000</pubDate>
      <link>https://dev.to/rosgluk/upgrading-to-go-127-what-breaks-what-to-adopt-first-3mpc</link>
      <guid>https://dev.to/rosgluk/upgrading-to-go-127-what-breaks-what-to-adopt-first-3mpc</guid>
      <description>&lt;p&gt;Go 1.27 shipped in August 2026, and for most modules the upgrade is a single &lt;code&gt;go.mod&lt;/code&gt; line followed by a &lt;code&gt;go fix&lt;/code&gt; pass — but a handful of changes bite silently. Here is what actually breaks and what to adopt first.&lt;/p&gt;

&lt;p&gt;The headline changes are structural: &lt;code&gt;encoding/json&lt;/code&gt; is now backed by the new json/v2 implementation under the hood, the &lt;code&gt;uuid&lt;/code&gt; package joined the standard library, and methods can finally declare their own type parameters. None of these force a rewrite, and the Go team kept the usual compatibility promise — almost everything compiles and runs unchanged.&lt;/p&gt;

&lt;p&gt;Not everything is quiet, though. Several &lt;code&gt;GODEBUG&lt;/code&gt; settings now fail the build at their old values, &lt;code&gt;compress/flate&lt;/code&gt; produces different bytes, &lt;code&gt;go test&lt;/code&gt; enforces a new &lt;code&gt;stdversion&lt;/code&gt; check, and compiler-generated symbol names changed. If you need a syntax refresher on the parts that did not move, the &lt;a href="https://www.glukhov.org/developer-tools/cheatsheets/golang-cheatsheet/" rel="noopener noreferrer"&gt;Go cheatsheet&lt;/a&gt; stays valid; this guide covers the rest, with commands and fixes for each item.&lt;/p&gt;

&lt;h2&gt;
  
  
  The upgrade path in one diagram
&lt;/h2&gt;

&lt;p&gt;The Go team recommends a build-first, fix-second order. &lt;code&gt;go fix&lt;/code&gt; rewrites code to new APIs and idioms, but only a clean build tells you whether any of the runtime-level changes hit you.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    A[Install Go 1.27 toolchain] --&amp;gt; B["go build ./..."]
    B --&amp;gt; C{Build passes?}
    C -- "No: removed GODEBUG value" --&amp;gt; D[Set final default or delete the line]
    D --&amp;gt; B
    C -- Yes --&amp;gt; E["go test ./..."]
    E --&amp;gt; F{"stdversion + vet clean?"}
    F -- "No: symbol too new" --&amp;gt; G[Raise go line or swap the symbol]
    G --&amp;gt; E
    F -- Yes --&amp;gt; H["go mod tidy"]
    H --&amp;gt; I["go fix ./..."]
    I --&amp;gt; J[Review JSON golden tests and diffs]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Start with the toolchain itself. If you manage toolchains through the &lt;code&gt;go&lt;/code&gt; command, either pin the version in &lt;code&gt;go.mod&lt;/code&gt; or install explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;install &lt;/span&gt;golang.org/dl/go1.27@latest
go1.27 download
go1.27 version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, set &lt;code&gt;GOTOOLCHAIN=go1.27.0&lt;/code&gt; in the environment, or add &lt;code&gt;toolchain go1.27.0&lt;/code&gt; to &lt;code&gt;go.mod&lt;/code&gt; and let the &lt;code&gt;go&lt;/code&gt; command fetch it. The module-focused companion on &lt;a href="https://www.glukhov.org/developer-tools/local-dev-platforms/go-workspace-structure/" rel="noopener noreferrer"&gt;Go workspace structure&lt;/a&gt; explains how the &lt;code&gt;go&lt;/code&gt; and &lt;code&gt;toolchain&lt;/code&gt; lines interact across multiple modules; the short version for a single module is below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: bump the &lt;code&gt;go&lt;/code&gt; line and clear removed GODEBUG settings
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go mod edit &lt;span class="nt"&gt;-go&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.27
go mod tidy
go build ./...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Raising the &lt;code&gt;go&lt;/code&gt; directive turns on the new language features (generic methods, promoted-field keys in struct literals, wider function type inference) and makes the &lt;code&gt;go&lt;/code&gt; command strict about &lt;code&gt;GODEBUG&lt;/code&gt; settings whose support has been removed. Go 1.27 removed these permanently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;asynctimerchan&lt;/code&gt; — timer and ticker channels are now always unbuffered&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gotypesalias&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tlsunsafeekm&lt;/code&gt;, &lt;code&gt;tlsrsakex&lt;/code&gt;, &lt;code&gt;tls3des&lt;/code&gt;, &lt;code&gt;tls10server&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;x509keypairleaf&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The new rule: the &lt;code&gt;go&lt;/code&gt; command still accepts these settings in &lt;code&gt;go.mod&lt;/code&gt; (&lt;code&gt;godebug&lt;/code&gt; entries) or in source (&lt;code&gt;//go:debug&lt;/code&gt; comments) if they are set to the final default value that existed before removal. An old value — the one that disabled a behaviour change — fails the build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;//go:debug asynctimerchan=0  // build error: old value of a removed setting&lt;/span&gt;
&lt;span class="c"&gt;//go:debug asynctimerchan=1  // accepted, but delete the line instead&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your build fails here, the code depended on pre-Go-1.23 buffered timer channels. The fix is in the code, not the config: check &lt;code&gt;Timer.Stop&lt;/code&gt; and &lt;code&gt;Timer.Reset&lt;/code&gt; usage against the &lt;a href="https://www.glukhov.org/app-architecture/code-architecture/go-context-cancellation-timeouts/" rel="noopener noreferrer"&gt;context, cancellation, and timeout patterns&lt;/a&gt; that rely on the synchronous channels. The same search applies to the TLS-related settings — anything pinning &lt;code&gt;tls3des=1&lt;/code&gt; or &lt;code&gt;tls10server=1&lt;/code&gt; was keeping old cipher suites alive, and Go 1.27 refuses to compile it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: run &lt;code&gt;go test&lt;/code&gt; — the stdversion trap
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;go build&lt;/code&gt; and &lt;code&gt;go test&lt;/code&gt; no longer agree. Go 1.27's &lt;code&gt;go test&lt;/code&gt; runs the &lt;code&gt;stdversion&lt;/code&gt; vet check by default, which reports standard-library symbols that are too new for the &lt;code&gt;go&lt;/code&gt; version in force in the referring file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// go.mod still says "go 1.26"&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CutLast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// added in Go 1.27&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a 1.27 toolchain, &lt;code&gt;go build&lt;/code&gt; succeeds — the compiler has &lt;code&gt;strings.CutLast&lt;/code&gt; — while &lt;code&gt;go test&lt;/code&gt; fails the vet step because &lt;code&gt;go.mod&lt;/code&gt; still declares 1.26. With a real 1.26 toolchain the file would not build at all. The lesson is procedural: never validate an upgrade with &lt;code&gt;go build&lt;/code&gt; alone; run the full test suite, then bump the &lt;code&gt;go&lt;/code&gt; line. The vet suite in &lt;code&gt;go test&lt;/code&gt; also picked up a new &lt;code&gt;printf&lt;/code&gt; finding: &lt;code&gt;fmt.Errorf("...: %w", p)&lt;/code&gt; where &lt;code&gt;p&lt;/code&gt; is a &lt;code&gt;*E&lt;/code&gt; and &lt;code&gt;E&lt;/code&gt; implements &lt;code&gt;error&lt;/code&gt; now warns, because wrapping a pointer produces type assertions and &lt;code&gt;errors.Is&lt;/code&gt; checks that fail. That failure mode shows up in error-handling code exactly as described in the &lt;a href="https://www.glukhov.org/app-architecture/code-architecture/go-error-handling-architecture/" rel="noopener noreferrer"&gt;Go error handling architecture&lt;/a&gt; patterns — check the operand type, not just the verb.&lt;/p&gt;

&lt;h2&gt;
  
  
  encoding/json: the v2 engine underneath
&lt;/h2&gt;

&lt;p&gt;The biggest under-the-hood change: &lt;code&gt;encoding/json/v2&lt;/code&gt; (experimental since Go 1.25 as &lt;code&gt;GOEXPERIMENT=jsonv2&lt;/code&gt;) graduated, and the classic &lt;code&gt;encoding/json&lt;/code&gt; package is now backed by the v2 implementation. For v1 callers, behaviour is preserved — nil slices still encode as &lt;code&gt;null&lt;/code&gt;, map keys still sort — with one visible difference: the exact text of some error messages changed. If tests or monitoring match on error strings, expect diffs.&lt;/p&gt;

&lt;p&gt;Three things can still go wrong:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Error text.&lt;/strong&gt; Any &lt;code&gt;strings.Contains(err.Error(), ...)&lt;/code&gt; assertions keyed to the old v1 wording need updating.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance-sensitive paths may shift.&lt;/strong&gt; Unmarshalling is faster, but the rewrite changes which operations allocate; benchmarks flagged in CI will show it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compatibility escape hatch.&lt;/strong&gt; &lt;code&gt;GOEXPERIMENT=nojsonv2&lt;/code&gt; restores the original v1 implementation at build time. It is temporary — plan to remove it, not to ship with it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A second-order effect for golden tests: &lt;code&gt;compress/flate&lt;/code&gt;'s encoder changed in the same release, so &lt;code&gt;archive/zip&lt;/code&gt;, &lt;code&gt;compress/gzip&lt;/code&gt;, &lt;code&gt;compress/zlib&lt;/code&gt;, and &lt;code&gt;image/png&lt;/code&gt; outputs may differ byte-for-byte from Go 1.26 even though they decode identically. Regenerate compressed fixtures rather than pinning old bytes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Migrating to the v2 API deliberately
&lt;/h3&gt;

&lt;p&gt;The v2 API is a separate import (&lt;code&gt;encoding/json/v2&lt;/code&gt;) with &lt;code&gt;Marshal&lt;/code&gt;, &lt;code&gt;Unmarshal&lt;/code&gt;, and an options system. The supported migration path is to make v2 calls behave like v1 first, then tighten selectively:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"encoding/json/v2"&lt;/span&gt;
    &lt;span class="s"&gt;"encoding/json/jsontext"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Step 1: identical behaviour to encoding/json — a safe, mechanical change&lt;/span&gt;
&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;jsonv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jsonv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultOptionsV1&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="c"&gt;// Step 2: opt in to one v2 default at a time&lt;/span&gt;
&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;jsonv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;jsonv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultOptionsV1&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;jsonv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FormatNilSliceAsNull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c"&gt;// nil slices now encode as []&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you drop &lt;code&gt;DefaultOptionsV1&lt;/code&gt;, v2 defaults apply, and they are stricter and more interoperable than v1:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Behaviour&lt;/th&gt;
&lt;th&gt;v1 (&lt;code&gt;encoding/json&lt;/code&gt;)&lt;/th&gt;
&lt;th&gt;v2 default&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Invalid UTF-8 in strings&lt;/td&gt;
&lt;td&gt;Replaced with U+FFFD&lt;/td&gt;
&lt;td&gt;Error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duplicate names in an object&lt;/td&gt;
&lt;td&gt;Allowed&lt;/td&gt;
&lt;td&gt;Error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nil slice / nil map&lt;/td&gt;
&lt;td&gt;&lt;code&gt;null&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;[]&lt;/code&gt; / &lt;code&gt;{}&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Struct field matching&lt;/td&gt;
&lt;td&gt;Case-insensitive fallback&lt;/td&gt;
&lt;td&gt;Case-sensitive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unknown object members&lt;/td&gt;
&lt;td&gt;Ignored&lt;/td&gt;
&lt;td&gt;Ignored (&lt;code&gt;RejectUnknownMembers&lt;/code&gt; to error)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Map key ordering&lt;/td&gt;
&lt;td&gt;Always sorted&lt;/td&gt;
&lt;td&gt;Unsorted by default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;time.Duration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Built-in string encoding&lt;/td&gt;
&lt;td&gt;No default encoding&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The unsorted map keys are the one that surprises golden-file tests: v2 skips the sort because sorting costs time. When you need stable output — fixtures, snapshot tests, hashed representations — pass &lt;code&gt;json.Deterministic(true)&lt;/code&gt;, which restores sorted output for maps and structs. The &lt;a href="https://www.glukhov.org/app-architecture/data-access/comparing-go-orms-gorm-ent-bun-sqlc/" rel="noopener noreferrer"&gt;ORM comparison&lt;/a&gt; article's models all carry &lt;code&gt;json&lt;/code&gt; tags; those tags keep working in v2, but the &lt;code&gt;format&lt;/code&gt; tag option was removed during the experiment and &lt;code&gt;inline&lt;/code&gt; was renamed to &lt;code&gt;embed&lt;/code&gt;, so grep your model definitions for both before migrating. For low-level needs — streaming, token-by-token processing, custom syntax handling — the companion &lt;code&gt;encoding/json/jsontext&lt;/code&gt; package exposes an &lt;code&gt;Encoder&lt;/code&gt;/&lt;code&gt;Decoder&lt;/code&gt; state machine over JSON text.&lt;/p&gt;

&lt;h2&gt;
  
  
  stdlib &lt;code&gt;uuid&lt;/code&gt;: dropping &lt;code&gt;github.com/google/uuid&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Go 1.27 adds &lt;code&gt;uuid&lt;/code&gt; to the standard library, implementing RFC 9562. The API surface covers the common cases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;    &lt;span class="c"&gt;// currently equivalent to NewV4&lt;/span&gt;
&lt;span class="n"&gt;v7&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewV7&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c"&gt;// time-sortable, 48-bit timestamp + random&lt;/span&gt;
&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"550e8400-e29b-41d4-a716-446655440000"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;github.com/google/uuid&lt;/code&gt; is the second most-depended-on Go module, so this migration matters at scale. The two APIs are close — types are &lt;code&gt;[16]byte&lt;/code&gt; values comparable with &lt;code&gt;==&lt;/code&gt; and usable as map keys, &lt;code&gt;String()&lt;/code&gt;, &lt;code&gt;MarshalText&lt;/code&gt;/&lt;code&gt;UnmarshalText&lt;/code&gt; round-trip — and the upstream project is preparing &lt;code&gt;go:fix inline&lt;/code&gt; shims so &lt;code&gt;go fix&lt;/code&gt; can rewrite &lt;code&gt;uuid.NewV4()&lt;/code&gt; calls to the standard package and delete the dependency.&lt;/p&gt;

&lt;p&gt;The interesting adoption decision is &lt;code&gt;NewV7&lt;/code&gt;. Version-7 UUIDs embed a millisecond timestamp in the most significant bits, so values sort in creation order (except when the system clock moves backwards). For database primary keys that means better B-tree locality than random UUIDv4, and &lt;code&gt;created_at&lt;/code&gt;-like ordering for free in the identifier itself — the same property that drives time-sortable key discussion in multi-tenant data models.&lt;/p&gt;

&lt;h2&gt;
  
  
  Language changes: generic methods and friends
&lt;/h2&gt;

&lt;p&gt;Generic methods are the release's most requested language feature: a method declaration may now declare its own type parameters.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Go 1.27: math/rand/v2 gained a generic method alongside the function&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Rand&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;intType&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="n"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Int&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two restrictions keep the type system decidable: interface methods may not declare type parameters, and interface methods cannot be implemented by generic methods. Alongside them, struct literal keys may now be any valid field selector (not just top-level field names), and function type inference generalizes to all contexts where a generic function is assigned to a matching function-type variable. None of these break existing code; they remove workarounds — package-level generic helper functions and explicit instantiations.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;go fix&lt;/code&gt; grew modernizers for adjacent cleanups: &lt;code&gt;atomictypes&lt;/code&gt;, &lt;code&gt;embedlit&lt;/code&gt;, &lt;code&gt;slicesbackward&lt;/code&gt;, and &lt;code&gt;unsafefuncs&lt;/code&gt;. One caution from the hands-on upgrade labs: run &lt;code&gt;go fix -fix=atomictypes&lt;/code&gt; deliberately rather than in bulk on libraries, since it can change the types of package-level variables that other packages reference. The &lt;code&gt;waitgroup&lt;/code&gt; modernizer was renamed to &lt;code&gt;waitgroupgo&lt;/code&gt;; note that &lt;code&gt;sync.WaitGroup.Go&lt;/code&gt; (from 1.25) already covers most of what it detects — see the &lt;a href="https://www.glukhov.org/app-architecture/code-architecture/bounding-concurrency-in-go/" rel="noopener noreferrer"&gt;bounded concurrency patterns&lt;/a&gt; for how it slots into worker-pool shutdown.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things that quietly break
&lt;/h2&gt;

&lt;p&gt;These are the changes that compile fine and still cost you a release day:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Function-literal symbol names.&lt;/strong&gt; The compiler now generates simpler, inlining-independent names for closures, and may merge identical function literals. Tests that assert on symbol names in tracebacks or profiles will fail; code that compares function code pointers for equality is exposed in more cases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;gofmt&lt;/code&gt; alignment.&lt;/strong&gt; A fix to column-alignment logic (it previously varied across CPU architectures) reflows a small number of already-formatted files. Expect whitespace-only diffs in a &lt;code&gt;gofmt ./...&lt;/code&gt; commit after the upgrade.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;go mod tidy&lt;/code&gt; rewrites &lt;code&gt;go.mod&lt;/code&gt;.&lt;/strong&gt; For modules on &lt;code&gt;go 1.27&lt;/code&gt;, &lt;code&gt;tidy&lt;/code&gt; now merges duplicate &lt;code&gt;require&lt;/code&gt; blocks into at most two blocks (direct, indirect), preserving comments. Diff your &lt;code&gt;go.mod&lt;/code&gt; and resolve any merge-conflict residue before the release commit, not after.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traceback labels.&lt;/strong&gt; Tracebacks for modules on &lt;code&gt;go 1.27&lt;/code&gt; include &lt;code&gt;runtime/pprof&lt;/code&gt; goroutine labels in the header line; disable with &lt;code&gt;GODEBUG=tracebacklabels=0&lt;/code&gt; if labels carry sensitive data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;go tool trace&lt;/code&gt; binds to localhost.&lt;/strong&gt; &lt;code&gt;-http=:6060&lt;/code&gt; now listens on localhost only; pass &lt;code&gt;-http=0.0.0.0:6060&lt;/code&gt; explicitly when you need remote access (and mind that a profiling port on a shared host is a risk — see the production profiling workflow for the hardened setup).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Experiments worth watching
&lt;/h2&gt;

&lt;p&gt;Two experimental packages shipped in 1.27, both enabled with &lt;code&gt;GOEXPERIMENT=simd&lt;/code&gt; at build time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;simd&lt;/code&gt;&lt;/strong&gt; — a portable, vector-size-agnostic API (&lt;code&gt;Int8s&lt;/code&gt;, &lt;code&gt;Float32s&lt;/code&gt;, ...) that maps to hardware instructions where available. It supports AVX/AVX2/AVX-512 on amd64, NEON on arm64, and WebAssembly SIMD. The standard library already uses this machinery internally: Go's Swiss Table map implementation benefits from it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;simd/archsimd&lt;/code&gt;&lt;/strong&gt; — the architecture-specific layer for code that needs to know the vector width.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the crypto side, &lt;code&gt;crypto/mldsa&lt;/code&gt; implements the post-quantum ML-DSA signature scheme from FIPS 204 in three parameter sets (&lt;code&gt;MLDSA44&lt;/code&gt;, &lt;code&gt;MLDSA65&lt;/code&gt;, &lt;code&gt;MLDSA87&lt;/code&gt;), integrated into &lt;code&gt;crypto/x509&lt;/code&gt; and offered as TLS 1.3 signature schemes. Hybrid post-quantum key exchange can now be forced via &lt;code&gt;Config.CurvePreferences&lt;/code&gt; even when the &lt;code&gt;tlsmlkem&lt;/code&gt;/&lt;code&gt;tlssecpmlkem&lt;/code&gt; &lt;code&gt;GODEBUG&lt;/code&gt; fallbacks are set. Neither is a migration item today, but both shape the TLS configuration surface your services will grow into.&lt;/p&gt;

&lt;h2&gt;
  
  
  New APIs worth adopting immediately
&lt;/h2&gt;

&lt;p&gt;A few additions solve everyday friction and cost nothing to adopt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// strings.CutLast / bytes.CutLast — the LastIndex dance, replaced&lt;/span&gt;
&lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CutLast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"a/b/c.tar.gz"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// httptest.NewTestServer — in-memory fake network, auto-cleanup&lt;/span&gt;
&lt;span class="n"&gt;srv&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;httptest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewTestServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// no real port, no defer srv.Close()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;httptest.NewTestServer&lt;/code&gt; creates an &lt;code&gt;httptest.Server&lt;/code&gt; backed by an in-memory network instead of a TCP listener and registers its own &lt;code&gt;t.Cleanup&lt;/code&gt;. Its real payoff is pairing with &lt;code&gt;testing/synctest&lt;/code&gt;: HTTP round-trips run in synthetic time, making timeout and retry tests fast and deterministic — the combination is demonstrated in the &lt;a href="https://www.glukhov.org/app-architecture/testing-architecture/testing-concurrent-go-code-synctest/" rel="noopener noreferrer"&gt;synctest concurrency testing&lt;/a&gt; article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upgrade checklist
&lt;/h2&gt;

&lt;p&gt;The order matters more than the items; each step assumes the previous one is green.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Small module&lt;/th&gt;
&lt;th&gt;Service / monorepo&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. Toolchain&lt;/td&gt;
&lt;td&gt;&lt;code&gt;go install golang.org/dl/go1.27@latest&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pin &lt;code&gt;toolchain go1.27.0&lt;/code&gt;; align CI images&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. &lt;code&gt;go.mod&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;go mod edit -go=1.27 &amp;amp;&amp;amp; go mod tidy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Same, plus resolve &lt;code&gt;require&lt;/code&gt;-block merge diffs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. Removed GODEBUG&lt;/td&gt;
&lt;td&gt;Grep &lt;code&gt;//go:debug&lt;/code&gt; and &lt;code&gt;godebug:&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Also audit deployed configs and flags&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. &lt;code&gt;go build ./...&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Fix compile errors&lt;/td&gt;
&lt;td&gt;Fix per-module; bisect failures&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5. &lt;code&gt;go test ./...&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Fix &lt;code&gt;stdversion&lt;/code&gt;/&lt;code&gt;printf&lt;/code&gt; findings&lt;/td&gt;
&lt;td&gt;Run before the &lt;code&gt;go&lt;/code&gt; bump to catch symbol drift&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6. &lt;code&gt;go fix ./...&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Review the diff&lt;/td&gt;
&lt;td&gt;One modernizer at a time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7. JSON/golden tests&lt;/td&gt;
&lt;td&gt;Regenerate fixtures&lt;/td&gt;
&lt;td&gt;Check &lt;code&gt;flate&lt;/code&gt;-based fixtures (zip/gzip/png) too&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8. &lt;code&gt;GOEXPERIMENT=nojsonv2&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Skip&lt;/td&gt;
&lt;td&gt;Temporary only; file issues for what forced it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;p&gt;Go 1.27's breaks cluster around edges most programs never touch — removed &lt;code&gt;GODEBUG&lt;/code&gt; opt-outs, symbol-name assertions, compressed-byte fixtures — while its wins land where every program lives: faster JSON, standard UUIDs, generic methods. A clean upgrade is mostly discipline: &lt;code&gt;go build&lt;/code&gt;, then &lt;code&gt;go test&lt;/code&gt;, then &lt;code&gt;go fix&lt;/code&gt;, in that order, with the &lt;code&gt;go&lt;/code&gt; line raised before the test run rather than after it. The &lt;code&gt;nojsonv2&lt;/code&gt; escape hatch and the &lt;code&gt;asynctimerchan&lt;/code&gt; acceptance-at-final-default rule buy time, but both are scheduled to disappear, so treat this upgrade as the deadline for the deferred fixes it inherits from 1.23–1.26.&lt;/p&gt;

</description>
      <category>go</category>
      <category>dev</category>
      <category>devops</category>
    </item>
    <item>
      <title>Bounding Concurrency in Go: errgroup, Worker Pools, and Backpressure</title>
      <dc:creator>Rost</dc:creator>
      <pubDate>Sat, 10 Oct 2026 07:53:44 +0000</pubDate>
      <link>https://dev.to/rosgluk/bounding-concurrency-in-go-errgroup-worker-pools-and-backpressure-1eh4</link>
      <guid>https://dev.to/rosgluk/bounding-concurrency-in-go-errgroup-worker-pools-and-backpressure-1eh4</guid>
      <description>&lt;p&gt;Unbounded fan-out is the default failure mode of Go concurrency: every &lt;code&gt;go&lt;/code&gt; statement is one more task in flight, with no ceiling and no owner. This article is about giving both.&lt;/p&gt;

&lt;p&gt;Cancellation and bounding are separate controls. Cancellation decides when work stops; bounding decides how much of it exists at once. A service can cancel perfectly and still fall over because ten thousand goroutines each decided, independently, that now was a good time to call the database.&lt;/p&gt;

&lt;p&gt;The pages around this one cover the other half of the problem. &lt;a href="https://www.glukhov.org/app-architecture/code-architecture/go-context-cancellation-timeouts/" rel="noopener noreferrer"&gt;Go context.Context Done Right&lt;/a&gt; treats context as the control plane for stopping work; here the subject is the capacity plane — &lt;code&gt;errgroup.SetLimit&lt;/code&gt;, semaphore channels, worker pools, and bounded queues — plus the one leak shape that survives &lt;code&gt;errgroup.Wait&lt;/code&gt; even when every cancel path looks wired up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five primitives, one decision table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Primitive&lt;/th&gt;
&lt;th&gt;Partial results&lt;/th&gt;
&lt;th&gt;Fail-fast&lt;/th&gt;
&lt;th&gt;Backpressure&lt;/th&gt;
&lt;th&gt;Dynamic work&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;sync.WaitGroup&lt;/code&gt; + &lt;code&gt;errors.Join&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;wait-all batches where every result counts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;errgroup.WithContext&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;fan-out where the first error ends the request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;errgroup.SetLimit(n)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes (blocks &lt;code&gt;Go&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;bounded fail-fast fan-out&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Semaphore channel&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;manual&lt;/td&gt;
&lt;td&gt;yes (blocks acquire)&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;custom admission control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worker pool&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;manual&lt;/td&gt;
&lt;td&gt;yes (queue fills)&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;steady streams, long-lived workers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two questions pick the row. First: when one task fails, do you still want the others' results? If yes, fail-fast group semantics discard work you needed. Second: does work arrive continuously, or is it a fixed batch you can count before launching? Groups handle batches; pools and queues handle streams.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;errors.Join&lt;/code&gt; is what makes the plain &lt;code&gt;WaitGroup&lt;/code&gt; row viable — it collects every worker's error instead of keeping only the first, which pairs with the boundary-translation rules in &lt;a href="https://www.glukhov.org/app-architecture/code-architecture/go-error-handling-architecture/" rel="noopener noreferrer"&gt;Go Error Handling Architecture&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The leak that survives errgroup.Wait
&lt;/h2&gt;

&lt;p&gt;The best-known errgroup failure mode has nothing to do with bounding. It is a cancellation gap, and it hides behind code that looks correct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gctx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;errgroup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// unbuffered&lt;/span&gt;

&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;// producer — nobody owns this goroutine&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nb"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}()&lt;/span&gt;

&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Go&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;// consumer — group member&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"save failed"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// returns at the consumer's error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the consumer returns, &lt;code&gt;gctx&lt;/code&gt; cancels and &lt;code&gt;Wait&lt;/code&gt; returns. The producer is parked on &lt;code&gt;results &amp;lt;- i&lt;/code&gt; — and context cancellation does not unblock a channel send. Nobody owns that goroutine, so it stays parked for the life of the process. Run this shape in a loop and the leak is exactly linear:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variant&lt;/th&gt;
&lt;th&gt;Live goroutines after 500 iterations&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Send without a &lt;code&gt;ctx.Done&lt;/code&gt; arm&lt;/td&gt;
&lt;td&gt;501 (+500 leaked, one per iteration)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Send wrapped in &lt;code&gt;select&lt;/code&gt; with &lt;code&gt;&amp;lt;-gctx.Done()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;502 (+1 baseline)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// the fix: every channel operation inside a cancellable task&lt;/span&gt;
&lt;span class="c"&gt;// gets a Done arm&lt;/span&gt;
&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;gctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same gap exists on the receive side. The rule is mechanical: every channel operation inside code that can be cancelled gets a &lt;code&gt;&amp;lt;-ctx.Done()&lt;/code&gt; arm, and every goroutine has an owner that waits on it or cancels it. &lt;code&gt;goleak&lt;/code&gt; in CI catches the cases review misses — it slots into the same automated quality gate as the tools in &lt;a href="https://www.glukhov.org/developer-tools/code-quality/linters-for-go/" rel="noopener noreferrer"&gt;Go Linters: Essential Tools for Code Quality&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Two related shapes are worth naming. If the producer &lt;em&gt;is&lt;/em&gt; a group member, the same code produces not a leak but a deadlock — &lt;code&gt;Wait&lt;/code&gt; blocks forever waiting for the parked send, which at least fails loudly. And a producer that sends with &lt;code&gt;select&lt;/code&gt; but receives on a channel whose consumer has quit leaks the same way; &lt;code&gt;Done&lt;/code&gt; arms belong on both sides.&lt;/p&gt;

&lt;h2&gt;
  
  
  errgroup.SetLimit: bounded fail-fast fan-out
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;SetLimit&lt;/code&gt; turns an errgroup into an admission-controlled group. Each &lt;code&gt;Go&lt;/code&gt; call beyond the limit blocks until a slot frees:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;errgroup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetLimit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Go&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three semantics matter in production. &lt;code&gt;Go&lt;/code&gt; blocks, so the loop above applies backpressure to the &lt;em&gt;producer&lt;/em&gt; — that is usually what you want, but it means the caller's goroutine can stall, so a request handler feeding a large batch needs its own timeout budget around the whole loop. &lt;code&gt;SetLimit(0)&lt;/code&gt; blocks every &lt;code&gt;Go&lt;/code&gt; call forever. And changing the limit while goroutines are active panics — the limit is fixed for the life of the group.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SetLimit&lt;/code&gt; inherits group semantics: the first error cancels the context and &lt;code&gt;Wait&lt;/code&gt; returns the first error, discarding results of work still in flight. It is the right primitive when failing the whole operation on first failure is correct — fetching one page of data, calling a set of independent health checks, fanning a request out to shards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Semaphore channels: backpressure as a feature
&lt;/h2&gt;

&lt;p&gt;A buffered channel of capacity &lt;em&gt;n&lt;/em&gt; is a semaphore with no dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;sem&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;wg&lt;/span&gt; &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaitGroup&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sem&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}{}&lt;/span&gt; &lt;span class="c"&gt;// blocks when 8 tasks are in flight&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;sem&lt;/span&gt; &lt;span class="p"&gt;}()&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The acquire happens before the &lt;code&gt;go&lt;/code&gt; statement, so goroutines are only ever created when a slot exists. The buffer size is the contract: it is simultaneously the concurrency ceiling and the queue of pending admissions.&lt;/p&gt;

&lt;p&gt;The primitive earns its place when you need admission control the groups do not express — context-aware acquisition, per-downstream pools, or partial results on failure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;sem&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}{}&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="c"&gt;// slot acquired&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="c"&gt;// caller gave up before getting in&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Err&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;Measured on the same workload, the semaphore channel and &lt;code&gt;SetLimit(8)&lt;/code&gt; finish within 1.5% of each other — the choice between them is semantics, not performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worker pools: for streams, not batches
&lt;/h2&gt;

&lt;p&gt;A fixed pool of long-lived workers separates admission (the queue) from execution (the workers):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;workers&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;chan&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;jobs&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;wg&lt;/span&gt; &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaitGroup&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;workers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;jobs&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="k"&gt;return&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;jobs&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pools fit work that arrives continuously — queue consumers, polling loops, request workers — where a per-batch group would be created and torn down endlessly. Since Go 1.25, &lt;code&gt;sync.WaitGroup.Go&lt;/code&gt; removes the &lt;code&gt;Add&lt;/code&gt;/&lt;code&gt;Done&lt;/code&gt; pairing boilerplate for the common shape. The shutdown contract is the part to get right: closing &lt;code&gt;jobs&lt;/code&gt; terminates workers after the queue drains; cancelling &lt;code&gt;ctx&lt;/code&gt; abandons queued work. Both are legitimate, but only one can be the documented behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bounded queues: block or drop
&lt;/h2&gt;

&lt;p&gt;A buffered channel is also a queue with a ceiling. Once the buffer fills, the producer blocks — and that moment is the design decision. Blocking propagates pressure upstream to whoever feeds the queue; dropping sheds load at the cost of lost work. A monitoring pipeline can drop; an order pipeline must block or spill to durable storage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="c"&gt;// admitted&lt;/span&gt;
&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;            &lt;span class="c"&gt;// queue full: drop policy lives here&lt;/span&gt;
    &lt;span class="n"&gt;dropped&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&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;Whatever the policy, make it explicit and measured. A silent &lt;code&gt;default&lt;/code&gt; branch is where events go to disappear.&lt;/p&gt;

&lt;h2&gt;
  
  
  What bounding costs — measured
&lt;/h2&gt;

&lt;p&gt;The same workload, 10,000 tasks of 5 ms simulated I/O each, Go 1.27.1:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Strategy&lt;/th&gt;
&lt;th&gt;Peak goroutines&lt;/th&gt;
&lt;th&gt;Wall time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Unbounded &lt;code&gt;go&lt;/code&gt; per task&lt;/td&gt;
&lt;td&gt;~10,000 launched&lt;/td&gt;
&lt;td&gt;18 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;errgroup.SetLimit(8)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;6.9 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Semaphore channel (cap 8)&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;6.6 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The unbounded run wins on wall time because nothing in the experiment pushes back — ten thousand timer goroutines simply sleep in parallel. That is exactly the trap. The cost of unbounded fan-out is never CPU in your own process; it is ten thousand simultaneous connections, a downstream that starts timing out under the burst, and a memory graph that follows the queue depth of whoever you are calling. On a real dependency, the unbounded run does not finish in 18 ms — it times out. The bounded runs pay 6.6 seconds because 10,000 tasks ÷ 8 slots × 5 ms is 6.25 s of unavoidable serialization, and the cap is the point: it turns an uncontrolled burst into a predictable 6.25-second drain.&lt;/p&gt;

&lt;p&gt;One refinement matters when the cap is higher than a handful of slots: keep the cap at or below what the downstream can sustain concurrently, and derive it from that limit (connection pool size, rate limit, worker capacity) rather than from a feeling about "reasonable" parallelism.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measuring and testing bounded code
&lt;/h2&gt;

&lt;p&gt;Three signals cover most of it. Goroutine count trends (&lt;code&gt;/sched/goroutines:goroutines&lt;/code&gt; exported via the Prometheus Go collector) catch leaks as a slope. Scheduler latency (&lt;code&gt;/sched/latencies:seconds&lt;/code&gt;) catches saturation — runnable goroutines waiting for CPU time is real pressure regardless of the count. A pprof delta of two goroutine dumps pinpoints where the parked goroutines live.&lt;/p&gt;

&lt;p&gt;For the tests, bounded workers are the case &lt;a href="https://www.glukhov.org/app-architecture/testing-architecture/testing-concurrent-go-code-synctest/" rel="noopener noreferrer"&gt;Testing Concurrent Go Code with synctest&lt;/a&gt; was built for: fake time makes a full 10,000-task drain run in milliseconds, &lt;code&gt;synctest.Wait&lt;/code&gt; replaces sleep-and-hope for quiescence, and a bubble fails fast when a worker stays durably blocked — the leak repro above dies in a test bubble instead of in production.&lt;/p&gt;

&lt;p&gt;At service scale, the same bounding question reappears between services, where the semaphore becomes a rate limit and the circuit breaker guards the downstream — &lt;a href="https://www.glukhov.org/app-architecture/integration-patterns/circuit-breaker-pattern-in-go/" rel="noopener noreferrer"&gt;Circuit Breaker Pattern in Go&lt;/a&gt; covers that layer, and &lt;a href="https://www.glukhov.org/app-architecture/integration-patterns/go-microservices-for-ai-ml-orchestration-patterns/" rel="noopener noreferrer"&gt;Go Microservices for AI/ML Orchestration&lt;/a&gt; shows the queue-backed forms the orchestration tier takes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing
&lt;/h2&gt;



&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    A[Work to run concurrently] --&amp;gt; B{Fixed batch or continuous stream?}
    B -- continuous stream --&amp;gt; C[Worker pool or bounded queue]
    B -- fixed batch --&amp;gt; D{Partial results needed&amp;lt;br/&amp;gt;on first failure?}
    D -- yes --&amp;gt; E[WaitGroup + errors.Join&amp;lt;br/&amp;gt;or semaphore channel]
    D -- no --&amp;gt; F[errgroup.WithContext]
    F --&amp;gt; G{Need a concurrency ceiling?}
    G -- yes --&amp;gt; H[errgroup.SetLimit]
    G -- no --&amp;gt; I[group without limit&amp;lt;br/&amp;gt;only when the batch is provably small]
    C --&amp;gt; J{Full queue: block or drop?}
    J -- block --&amp;gt; K[Backpressure upstream]
    J -- drop --&amp;gt; L[Shed load, count the drops]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The table and the flowchart compress to one habit: before writing &lt;code&gt;go&lt;/code&gt;, name the ceiling and the owner. The ceiling is &lt;code&gt;SetLimit&lt;/code&gt;, a semaphore, or a bounded queue; the owner is a &lt;code&gt;Wait&lt;/code&gt;, a &lt;code&gt;ctx.Done&lt;/code&gt; arm on every channel operation, or both. Every goroutine-creation site that can answer those two names in one glance is one that will not page anyone at 3 a.m.&lt;/p&gt;

&lt;h2&gt;
  
  
  Useful links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.glukhov.org/app-architecture/" rel="noopener noreferrer"&gt;App Architecture hub — API design, code structure, and integration patterns&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.glukhov.org/app-architecture/code-architecture/go-context-cancellation-timeouts/" rel="noopener noreferrer"&gt;Go context.Context Done Right: Cancellation, Timeouts, and Values&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>api</category>
      <category>architecture</category>
      <category>dev</category>
      <category>go</category>
    </item>
    <item>
      <title>Clone a VM in Virt-Manager with virt-clone and virt-sysprep</title>
      <dc:creator>Rost</dc:creator>
      <pubDate>Fri, 09 Oct 2026 11:36:19 +0000</pubDate>
      <link>https://dev.to/rosgluk/clone-a-vm-in-virt-manager-with-virt-clone-and-virt-sysprep-5g07</link>
      <guid>https://dev.to/rosgluk/clone-a-vm-in-virt-manager-with-virt-clone-and-virt-sysprep-5g07</guid>
      <description>&lt;p&gt;Virt-Manager (the libvirt GUI) copies a KVM disk and assigns the clone a UUID and MAC. The guest keeps its machine-id and SSH host keys until virt-sysprep clears them.&lt;/p&gt;

&lt;p&gt;This walkthrough uses an Ubuntu 26.04 guest and a 14 GB qcow2 image stored at &lt;code&gt;/mnt/extra2/vms&lt;/code&gt;. The disk commands are the same for other Linux guests. virt-sysprep does not operate on Windows guests.&lt;/p&gt;

&lt;p&gt;The host already needs KVM and libvirt. &lt;a href="https://www.glukhov.org/developer-tools/virtualization/install-kvm-ubuntu-24-04/" rel="noopener noreferrer"&gt;Install KVM on Ubuntu 24.04&lt;/a&gt; is that setup, and it pulls in &lt;code&gt;virtinst&lt;/code&gt;, which provides &lt;code&gt;virt-clone&lt;/code&gt;. &lt;code&gt;virt-sysprep&lt;/code&gt;, &lt;code&gt;virt-customize&lt;/code&gt;, &lt;code&gt;virt-cat&lt;/code&gt;, and &lt;code&gt;guestfish&lt;/code&gt; come from a separate package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; libguestfs-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What actually clones a VM
&lt;/h2&gt;

&lt;p&gt;libvirt splits the job across three tools. &lt;code&gt;virt-clone&lt;/code&gt; copies the disk images and defines a new domain with the same virtual hardware. It changes the domain name, the UUID, and the host-side MAC. Passwords, the hostname, static addresses, and files inside the guest are unchanged, which is the behaviour &lt;a href="https://manpages.ubuntu.com/manpages/resolute/man1/virt-clone.1.html" rel="noopener noreferrer"&gt;virt-clone(1)&lt;/a&gt; documents.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;virt-sysprep&lt;/code&gt; opens the disk with libguestfs and edits the guest filesystem. The &lt;a href="https://libguestfs.org/virt-sysprep.1.html" rel="noopener noreferrer"&gt;virt-sysprep(1)&lt;/a&gt; manual is explicit that the edit is in place: there is no output-file flag, so the image you pass to &lt;code&gt;-a&lt;/code&gt; is the image that changes. Copy or clone first if you still need the source.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;qemu-img info&lt;/code&gt; reports whether that file is standalone or the top of a backing chain. &lt;code&gt;qemu-img convert&lt;/code&gt; writes a new file with the chain resolved.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A[guest shut down] --&amp;gt;|virt-clone| B[new domain and disk]
    B --&amp;gt;|qemu-img info| C{backing file?}
    C --&amp;gt;|yes| D[qemu-img convert]
    C --&amp;gt;|no| E[standalone qcow2]
    D --&amp;gt; E
    E --&amp;gt;|copy, then virt-sysprep| F[template]
    F --&amp;gt;|per-instance customize| G[imported guest]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Flatten before sysprep when a backing line is present. Sysprep the copy, then derive each running guest from that file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloning the disk in Virt-Manager
&lt;/h2&gt;

&lt;p&gt;Shut the guest down. &lt;code&gt;virt-clone&lt;/code&gt; rejects a running domain, and a copy of a live qcow2 is not a consistent filesystem.&lt;/p&gt;

&lt;p&gt;In Virt-Manager, right-click the guest and choose &lt;strong&gt;Clone&lt;/strong&gt;. Set the new name. On the storage step, give each disk a new path. Leaving the original path in place defines a second domain that opens the same file, so both guests write the same blocks.&lt;/p&gt;

&lt;p&gt;The new file lands beside the source. A default pool uses &lt;code&gt;/var/lib/libvirt/images/&lt;/code&gt;. A disk that already lives on &lt;code&gt;/mnt/extra2/vms&lt;/code&gt; is cloned into that directory. Note the path &lt;code&gt;virt-clone&lt;/code&gt; prints, or read it back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh domblklist ubuntu26.04-base14gb-clone
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command-line equivalent, with the name and the disk path chosen explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virt-clone &lt;span class="nt"&gt;--original&lt;/span&gt; ubuntu26.04-base14gb &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; ubuntu26.04-base14gb-clone &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--file&lt;/span&gt; /mnt/extra2/vms/ubuntu26.04-base14gb-clone.qcow2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--auto-clone&lt;/code&gt; invents the name and the disk path. That is convenient for a throwaway copy. A template path is easier to keep when you pass &lt;code&gt;--name&lt;/code&gt; and &lt;code&gt;--file&lt;/code&gt; yourself. Omit &lt;code&gt;--mac&lt;/code&gt;, or pass &lt;code&gt;--mac RANDOM&lt;/code&gt;, and virt-clone generates an address in the QEMU range &lt;code&gt;52:54:00:xx:xx:xx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--reflink&lt;/code&gt; asks for a lightweight copy. The Ubuntu 26.04 virt-clone manual limits that flag to raw images on the same btrfs filesystem, so it skips these qcow2 files. A qcow2 overlay is a separate setup, covered with &lt;code&gt;qemu-img create -b&lt;/code&gt; below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does your clone have a backing file?
&lt;/h2&gt;

&lt;p&gt;Run this on the new disk before you treat it as independent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;qemu-img info &lt;span class="nt"&gt;--backing-chain&lt;/span&gt; /mnt/extra2/vms/ubuntu26.04-base14gb-clone.qcow2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A standalone image lists &lt;code&gt;file format&lt;/code&gt;, &lt;code&gt;virtual size&lt;/code&gt;, and &lt;code&gt;disk size&lt;/code&gt;. An overlay also lists a &lt;code&gt;backing file:&lt;/code&gt; line pointing at the image it reads through. &lt;code&gt;virtual size&lt;/code&gt; is the guest's disk size, here about 14 GB. &lt;code&gt;disk size&lt;/code&gt; is how much space the file currently occupies, which is small for a fresh overlay and grows as the guest writes.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;virt-clone&lt;/code&gt; is not the usual source of that line. An overlay comes from &lt;code&gt;qemu-img create -b&lt;/code&gt;, from a disk-only snapshot, or from a tool that created a qcow2 delta on purpose. The info check is what tells those cases apart from a full copy.&lt;/p&gt;

&lt;p&gt;Keep the overlay only when the base will stay where it is and will not be written again. Every write from the clone lands in the small file; reads that miss in the overlay come from the base. Moving or editing the base breaks every guest that names it.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TB
    subgraph linked
        O[base.qcow2 read-only]
        C1[vm1.qcow2 deltas only]
        C2[vm2.qcow2 deltas only]
        C1 --&amp;gt;|backing file| O
        C2 --&amp;gt;|backing file| O
    end
    subgraph flat
        F1[vm1-flat.qcow2 standalone]
        F2[vm2-flat.qcow2 standalone]
    end&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;To make a standalone file, convert the overlay. &lt;code&gt;-p&lt;/code&gt; prints progress. The destination is a new file; the overlay and the base are left as they are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;qemu-img convert &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nt"&gt;-O&lt;/span&gt; qcow2 &lt;span class="se"&gt;\&lt;/span&gt;
  /mnt/extra2/vms/ubuntu26.04-base14gb-clone.qcow2 &lt;span class="se"&gt;\&lt;/span&gt;
  /mnt/extra2/vms/ubuntu26.04-base14gb-flat.qcow2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;qemu-img info&lt;/code&gt; on the flat file and confirm there is no &lt;code&gt;backing file:&lt;/code&gt; line. A template is that standalone file. Overlays are how you stamp guests from it later, not the template itself.&lt;/p&gt;

&lt;p&gt;A btrfs reflink is a third case. &lt;code&gt;cp --reflink=auto&lt;/code&gt; can share physical extents between two files, and &lt;code&gt;qemu-img info&lt;/code&gt; still shows no backing file, because each path is a complete image. That copy is independent at the qcow2 layer even though the filesystem has not duplicated every block yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  virt-sysprep: resetting the identity
&lt;/h2&gt;

&lt;p&gt;Copy the standalone image, then sysprep the copy. &lt;code&gt;--reflink=auto&lt;/code&gt; shares extents when the filesystem supports it and otherwise makes a normal copy, including on ext4. &lt;code&gt;-n&lt;/code&gt; is a dry run that discards its writes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;--reflink&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;auto &lt;span class="se"&gt;\&lt;/span&gt;
  /mnt/extra2/vms/ubuntu26.04-base14gb-flat.qcow2 &lt;span class="se"&gt;\&lt;/span&gt;
  /mnt/extra2/vms/ubuntu26.04-base14gb-clean.qcow2

virt-sysprep &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /mnt/extra2/vms/ubuntu26.04-base14gb-clean.qcow2
virt-sysprep &lt;span class="nt"&gt;-a&lt;/span&gt; /mnt/extra2/vms/ubuntu26.04-base14gb-clean.qcow2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--hostname&lt;/span&gt; ubuntu-template
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a default Ubuntu pool the image is often mode &lt;code&gt;0600&lt;/code&gt; and owned by &lt;code&gt;libvirt-qemu&lt;/code&gt;. The manual recommends giving your user write access rather than running as root. When the qemu user owns the file, &lt;code&gt;sudo virt-sysprep -a ...&lt;/code&gt; is the invocation that can open it. The guest must be shut down, and nothing else should have the image open.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--hostname&lt;/code&gt; is a customize option, and customize is part of the default operation set, so it runs in the same invocation. There is no separate default operation that clears &lt;code&gt;/etc/hostname&lt;/code&gt; on its own. &lt;code&gt;--operations&lt;/code&gt; replaces that default set when you pass it; &lt;code&gt;--operations defaults,-ssh-userdir&lt;/code&gt; keeps the defaults and turns one operation off.&lt;/p&gt;

&lt;p&gt;These are the operations that decide whether the clone collides with the original. The list is the one in the current &lt;a href="https://libguestfs.org/virt-sysprep.1.html" rel="noopener noreferrer"&gt;virt-sysprep manual&lt;/a&gt;, which is worth reading because a short &lt;code&gt;--operations&lt;/code&gt; list silently drops the rest:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Effect on the guest&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;machine-id&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;Clears the machine ID. systemd generates a new one on the next boot when &lt;code&gt;/etc/machine-id&lt;/code&gt; is empty.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ssh-hostkeys&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;Removes &lt;code&gt;/etc/ssh/ssh_host_*&lt;/code&gt;. The next boot creates new host keys.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ssh-userdir&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;Removes &lt;code&gt;.ssh&lt;/code&gt; under &lt;code&gt;/root&lt;/code&gt; and &lt;code&gt;/home/*&lt;/code&gt;, including &lt;code&gt;authorized_keys&lt;/code&gt; and private keys stored in the guest.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dhcp-client-state&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;Removes DHCP client leases.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;udev-persistent-net&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;Removes udev rules that map an old MAC to a fixed name such as &lt;code&gt;eth0&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;net-hwaddr&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;Removes &lt;code&gt;HWADDR&lt;/code&gt; from Fedora and RHEL &lt;code&gt;ifcfg-*&lt;/code&gt; files. It does not edit Ubuntu netplan.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lvm-uuids, lvm-system-devices&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;Assigns new LVM UUIDs and removes &lt;code&gt;/etc/lvm/devices/system.devices&lt;/code&gt;, which otherwise pins the guest to the source disk's WWIDs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;logfiles, bash-history, tmp-files&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;Removes logs, shell history, and temp files.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;ssh-userdir&lt;/code&gt; is the one that surprises people cloning a machine they still log into. The template should not carry the source's private keys, so leave the operation on and inject a key per instance later. For a one-off copy that must keep the existing &lt;code&gt;authorized_keys&lt;/code&gt;, pass &lt;code&gt;--operations defaults,-ssh-userdir&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;user-account&lt;/code&gt; is not in the default set, and passwords stay unless you pass &lt;code&gt;--password&lt;/code&gt; or &lt;code&gt;--root-password&lt;/code&gt;. &lt;code&gt;fs-uuids&lt;/code&gt; is also off by default. The manual says it does not update every reference, including &lt;code&gt;/etc/fstab&lt;/code&gt;, and that enabling it is likely to make the guest unbootable.&lt;/p&gt;

&lt;p&gt;Ubuntu Server images usually have cloud-init, and virt-sysprep has no cloud-init operation. An old instance id under &lt;code&gt;/var/lib/cloud&lt;/code&gt; makes the clone skip first-boot setup. Clean it on the template, and tell cloud-init to leave the hostname that &lt;code&gt;--hostname&lt;/code&gt; wrote:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virt-customize &lt;span class="nt"&gt;-a&lt;/span&gt; /mnt/extra2/vms/ubuntu26.04-base14gb-clean.qcow2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--run-command&lt;/span&gt; &lt;span class="s1"&gt;'if command -v cloud-init &amp;gt;/dev/null 2&amp;gt;&amp;amp;1; then cloud-init clean --logs --seed; fi'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--write&lt;/span&gt; &lt;span class="s1"&gt;'/etc/cloud/cloud.cfg.d/99-preserve-hostname.cfg:preserve_hostname: true'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same manual warns against cloning a guest that uses internal full-disk encryption for distribution: every copy shares the volume key. It also notes that deleted files are unlinked, not scrubbed. &lt;code&gt;--scrub&lt;/code&gt; overwrites a named file, and &lt;code&gt;virt-sparsify&lt;/code&gt; can drop freed space from the image.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the clone gets its IP address
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;virt-sysprep&lt;/code&gt; has no operation that rewrites an address. A guest that used DHCP asks again. The clone has a new MAC, an empty machine-id, and no old lease file, so the DHCP server on libvirt's &lt;code&gt;default&lt;/code&gt; network treats it as a new client.&lt;/p&gt;

&lt;p&gt;A static &lt;code&gt;addresses:&lt;/code&gt; block in netplan is copied with the disk, and both guests then claim it. Read the files before the first boot. &lt;code&gt;virt-ls&lt;/code&gt; and &lt;code&gt;virt-cat&lt;/code&gt; are the one-shot tools; &lt;code&gt;guestfish --ro -a &amp;lt;disk&amp;gt; -i&lt;/code&gt; is the same inspection as a shell, after which &lt;code&gt;ls /etc/netplan&lt;/code&gt; and &lt;code&gt;cat&lt;/code&gt; run against the guest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virt-ls &lt;span class="nt"&gt;-a&lt;/span&gt; /mnt/extra2/vms/ubuntu26.04-base14gb-clean.qcow2 /etc/netplan
virt-cat &lt;span class="nt"&gt;-a&lt;/span&gt; /mnt/extra2/vms/ubuntu26.04-base14gb-clean.qcow2 &lt;span class="se"&gt;\&lt;/span&gt;
  /etc/netplan/00-installer-config.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Installer images often use &lt;code&gt;00-installer-config.yaml&lt;/code&gt; or &lt;code&gt;01-netcfg.yaml&lt;/code&gt;. Cloud images use &lt;code&gt;50-cloud-init.yaml&lt;/code&gt;, which cloud-init will recreate unless network configuration is disabled. Netplan merges every YAML file in the directory, so a leftover static file still applies after you add a DHCP file.&lt;/p&gt;

&lt;p&gt;To make the template a DHCP client, remove those files and upload one replacement. Use the interface name &lt;code&gt;virt-cat&lt;/code&gt; showed. On a guest installed under KVM that name is often &lt;code&gt;ens3&lt;/code&gt; or &lt;code&gt;enp1s0&lt;/code&gt;, and &lt;code&gt;virt-clone&lt;/code&gt; keeps the same virtual PCI slot, so the name stays valid:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/99-dhcp.yaml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
network:
  version: 2
  ethernets:
    ens3:
      dhcp4: true
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;virt-customize &lt;span class="nt"&gt;-a&lt;/span&gt; /mnt/extra2/vms/ubuntu26.04-base14gb-clean.qcow2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--delete&lt;/span&gt; &lt;span class="s1"&gt;'/etc/netplan/*.yaml'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--upload&lt;/span&gt; /tmp/99-dhcp.yaml:/etc/netplan/99-dhcp.yaml &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--write&lt;/span&gt; &lt;span class="s1"&gt;'/etc/cloud/cloud.cfg.d/99-disable-network.cfg:network: {config: disabled}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a static address, gateway, and DNS, use the same upload with the YAML from &lt;a href="https://www.glukhov.org/developer-tools/terminals-shell/how-to-change-static-ip-address-in-ubuntu/" rel="noopener noreferrer"&gt;How to Change a Static IP Address in Ubuntu Server&lt;/a&gt;. Each instance then needs its own file, applied to the instance disk rather than the template.&lt;/p&gt;

&lt;p&gt;The name in the YAML has to exist inside the guest. A disk moved from physical hardware often names the NIC &lt;code&gt;enp2s0&lt;/code&gt;, while the same system under virtio presents a different name, and netplan then configures an interface that is not there. For a template that should boot on more than one machine type, pin the kernel to &lt;code&gt;eth0&lt;/code&gt; and use that key in netplan. Ubuntu sources &lt;code&gt;/etc/default/grub.d/*.cfg&lt;/code&gt; while building &lt;code&gt;grub.cfg&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virt-customize &lt;span class="nt"&gt;-a&lt;/span&gt; /mnt/extra2/vms/ubuntu26.04-base14gb-clean.qcow2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--write&lt;/span&gt; &lt;span class="s1"&gt;'/etc/default/grub.d/99-eth0.cfg:GRUB_CMDLINE_LINUX="$GRUB_CMDLINE_LINUX net.ifnames=0 biosdevname=0"'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--run-command&lt;/span&gt; &lt;span class="s1"&gt;'update-grub'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;update-grub&lt;/code&gt; runs inside the libguestfs appliance. If &lt;code&gt;grub-probe&lt;/code&gt; cannot see the boot disk there, the command fails and the cmdline is unchanged. In that case, set the interface name in netplan to the name the guest already uses, and skip the &lt;code&gt;eth0&lt;/code&gt; switch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Per-instance customization with virt-customize
&lt;/h2&gt;

&lt;p&gt;Boot instances from copies of the clean file. Do not point a domain at the template itself.&lt;/p&gt;

&lt;p&gt;A full copy has no runtime dependency on the template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;--reflink&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;auto &lt;span class="se"&gt;\&lt;/span&gt;
  /mnt/extra2/vms/ubuntu26.04-base14gb-clean.qcow2 &lt;span class="se"&gt;\&lt;/span&gt;
  /mnt/extra2/vms/ubuntu26.04-vm1.qcow2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An overlay stores only the blocks vm1 writes. &lt;code&gt;-F qcow2&lt;/code&gt; records the base format so QEMU does not have to probe it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;qemu-img create &lt;span class="nt"&gt;-f&lt;/span&gt; qcow2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-b&lt;/span&gt; /mnt/extra2/vms/ubuntu26.04-base14gb-clean.qcow2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; qcow2 &lt;span class="se"&gt;\&lt;/span&gt;
  /mnt/extra2/vms/ubuntu26.04-vm1.qcow2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The base path is stored inside the overlay. It has to keep that path, and it has to stay read-only, for every guest built this way.&lt;/p&gt;

&lt;p&gt;The virt-sysprep security notes describe a second pass on each new disk. Sysprep writes a random seed into the template; guests cloned from that file would otherwise start with the same seed, which makes first-boot SSH host keys and TCP sequence numbers predictable. &lt;code&gt;--enable customize&lt;/code&gt; rewrites the seed. &lt;code&gt;--hostname&lt;/code&gt; and &lt;code&gt;--ssh-inject&lt;/code&gt; are customize options, so they belong on the same command. Substitute the account that actually exists in the image for &lt;code&gt;ubuntu&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virt-sysprep &lt;span class="nt"&gt;--enable&lt;/span&gt; customize &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-a&lt;/span&gt; /mnt/extra2/vms/ubuntu26.04-vm1.qcow2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--hostname&lt;/span&gt; vm1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--ssh-inject&lt;/span&gt; &lt;span class="s2"&gt;"ubuntu:file:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/.ssh/id_ed25519.pub"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hand the instance file to libvirt, not the template. If you created it as your user outside a storage pool, &lt;code&gt;libvirt-qemu&lt;/code&gt; needs read access or the domain fails at startup with a permission error on the qcow2.&lt;/p&gt;

&lt;h2&gt;
  
  
  Importing into Virt-Manager
&lt;/h2&gt;

&lt;p&gt;For each instance disk, open Virt-Manager, choose &lt;strong&gt;New Virtual Machine&lt;/strong&gt;, and select &lt;strong&gt;Import existing disk image&lt;/strong&gt;. Point it at &lt;code&gt;ubuntu26.04-vm1.qcow2&lt;/code&gt;. The OS entry should be Ubuntu 26.04 when the host's osinfo database has it.&lt;/p&gt;

&lt;p&gt;The matching short id is &lt;code&gt;ubuntu26.04&lt;/code&gt;. Check the host database before relying on it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;osinfo-query os | &lt;span class="nb"&gt;grep &lt;/span&gt;ubuntu26
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that prints nothing, the installed database is older than the 26.04 entry. &lt;code&gt;--os-variant ubuntu24.04&lt;/code&gt; still selects a virtio-friendly machine type for a guest that is already installed. &lt;code&gt;--import&lt;/code&gt; tells virt-install the disk already contains an OS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;virt-install &lt;span class="nt"&gt;--name&lt;/span&gt; ubuntu26.04-vm1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--memory&lt;/span&gt; 4096 &lt;span class="nt"&gt;--vcpus&lt;/span&gt; 2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--disk&lt;/span&gt; &lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/mnt/extra2/vms/ubuntu26.04-vm1.qcow2,format&lt;span class="o"&gt;=&lt;/span&gt;qcow2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--import&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--os-variant&lt;/span&gt; ubuntu26.04 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt; &lt;span class="nv"&gt;network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;default &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--noautoconsole&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--network network=default&lt;/code&gt; attaches the NAT network whose dnsmasq instance hands out the DHCP lease. After boot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh domifaddr ubuntu26.04-vm1 &lt;span class="nt"&gt;--source&lt;/span&gt; lease
virsh dumpxml ubuntu26.04-vm1 | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;mac"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--source lease&lt;/code&gt; reads that dnsmasq table. It stays empty for a static address, for a bridge, or for a NIC that is not on a libvirt virtual network. Those guests need &lt;code&gt;qemu-guest-agent&lt;/code&gt; in the image and &lt;code&gt;virsh domifaddr ubuntu26.04-vm1 --source agent&lt;/code&gt;, or an ARP lookup with &lt;code&gt;--source arp&lt;/code&gt;. &lt;code&gt;virsh net-dhcp-leases default&lt;/code&gt; lists every lease on the NAT network when you want the pool view rather than one domain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing a clone strategy: full copy, overlay, or reflink
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Strategy&lt;/th&gt;
&lt;th&gt;Space per VM&lt;/th&gt;
&lt;th&gt;Time to create&lt;/th&gt;
&lt;th&gt;Constraint&lt;/th&gt;
&lt;th&gt;Use when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Full copy, or &lt;code&gt;cp --reflink=auto&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;about 14 GB logical; reflink shares blocks until they change&lt;/td&gt;
&lt;td&gt;minutes, or near-instant with reflink&lt;/td&gt;
&lt;td&gt;reflink needs a filesystem that supports it, such as btrfs&lt;/td&gt;
&lt;td&gt;a guest that should outlive the template&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;qcow2 overlay&lt;/td&gt;
&lt;td&gt;megabytes at first&lt;/td&gt;
&lt;td&gt;seconds&lt;/td&gt;
&lt;td&gt;base path must remain, and the base must stay read-only&lt;/td&gt;
&lt;td&gt;many similar guests from one sealed image&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;virt-clone --reflink&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;raw image, shared extents&lt;/td&gt;
&lt;td&gt;near-instant&lt;/td&gt;
&lt;td&gt;raw disks on the same btrfs filesystem; qcow2 is skipped&lt;/td&gt;
&lt;td&gt;raw images already stored on btrfs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A guest you expect to keep for months is a full copy or a reflink. A short-lived guest can be an overlay, as long as the base file is still there when it runs. &lt;a href="https://www.glukhov.org/developer-tools/virtualization/proxmox-virtualization/" rel="noopener noreferrer"&gt;Proxmox&lt;/a&gt; is the other self-hosted path when you want a datacenter-style template and clone model on its own web UI. &lt;a href="https://www.glukhov.org/developer-tools/virtualization/vm-manager-multipass-cheatsheet/" rel="noopener noreferrer"&gt;Multipass&lt;/a&gt; launches Ubuntu cloud images for a single user and applies cloud-init itself, which is a different workflow from sealing a disk you already installed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting: when the clone will not boot
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;No address.&lt;/strong&gt; &lt;code&gt;virt-cat&lt;/code&gt; the netplan file and compare the interface key with &lt;code&gt;ip link&lt;/code&gt; on the console. A &lt;code&gt;match: macaddress&lt;/code&gt; entry still set to the source MAC drops the new NIC. A key that names an interface the guest does not have does the same. Fix the file on the disk, or apply the &lt;code&gt;eth0&lt;/code&gt; cmdline above and rebuild the instance from the template.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Address up, same as the original.&lt;/strong&gt; The static &lt;code&gt;addresses:&lt;/code&gt; block is still in one of the netplan files. &lt;code&gt;virt-sysprep&lt;/code&gt; did not remove it. Upload a replacement and delete the other YAML files in &lt;code&gt;/etc/netplan&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SSH says the host key changed, or accepts the clone as the original.&lt;/strong&gt; Host keys are shared when &lt;code&gt;ssh-hostkeys&lt;/code&gt; did not run on the file the domain opens. Confirm with &lt;code&gt;virsh domblklist&lt;/code&gt; that the domain uses the sysprepped disk, then check &lt;code&gt;qemu-img info&lt;/code&gt; so you did not sysprep an overlay while the guest boots from a different path. If the clone kept the original IP on purpose, clients that stored the old key in &lt;code&gt;known_hosts&lt;/code&gt; will warn until that line is removed. That warning is the regeneration the manual describes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key login fails on a clone of a machine you could reach before.&lt;/strong&gt; Default sysprep removed &lt;code&gt;.ssh&lt;/code&gt; from the home directories. Inject a key with &lt;code&gt;--ssh-inject&lt;/code&gt;, or re-run with &lt;code&gt;--operations defaults,-ssh-userdir&lt;/code&gt; if this copy should have kept them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LVM volumes missing after boot.&lt;/strong&gt; The guest has &lt;code&gt;/etc/lvm/devices/system.devices&lt;/code&gt; from the source, and the virtual disk's WWID changed. The default &lt;code&gt;lvm-system-devices&lt;/code&gt; operation removes that file. A hand-picked &lt;code&gt;--operations&lt;/code&gt; list has to include it, and &lt;code&gt;lvm-uuids&lt;/code&gt;, when the guest uses LVM.&lt;/p&gt;

&lt;h2&gt;
  
  
  One clone or a template
&lt;/h2&gt;

&lt;p&gt;A single extra machine is &lt;code&gt;virt-clone&lt;/code&gt;, a backing-file check, and &lt;code&gt;virt-sysprep&lt;/code&gt; on the new disk. A fleet is one sealed standalone file, then a copy or an overlay per guest, with &lt;code&gt;--enable customize&lt;/code&gt; on that guest's own disk before it boots.&lt;/p&gt;

&lt;p&gt;Overlays make the sealed file part of the runtime path of every guest that names it. Full copies and reflinks do not. Pick that dependency with the lifetime of the guest, and leave &lt;code&gt;fs-uuids&lt;/code&gt; off so the bootloader and &lt;code&gt;/etc/fstab&lt;/code&gt; still match the filesystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Useful links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.glukhov.org/developer-tools/" rel="noopener noreferrer"&gt;Developer Tools: The Complete Guide to Modern Development Workflows&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.glukhov.org/developer-tools/local-dev-platforms/install-linux-ubuntu-24-04/" rel="noopener noreferrer"&gt;How to Install Ubuntu 24.04 &amp;amp; useful tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.glukhov.org/developer-tools/virtualization/gnome-boxes-linux-virtual-machines-manager/" rel="noopener noreferrer"&gt;GNOME Boxes: Features, Advantages, Challenges, and Alternatives&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://manpages.ubuntu.com/manpages/resolute/man1/virt-clone.1.html" rel="noopener noreferrer"&gt;virt-clone(1)&lt;/a&gt; on Ubuntu 26.04, including &lt;code&gt;--auto-clone&lt;/code&gt;, &lt;code&gt;--file&lt;/code&gt;, and &lt;code&gt;--reflink&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://libguestfs.org/virt-sysprep.1.html" rel="noopener noreferrer"&gt;virt-sysprep(1)&lt;/a&gt; operations, in-place edits, and the random-seed note&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://libguestfs.org/virt-customize.1.html" rel="noopener noreferrer"&gt;virt-customize(1)&lt;/a&gt; for &lt;code&gt;--hostname&lt;/code&gt;, &lt;code&gt;--ssh-inject&lt;/code&gt;, &lt;code&gt;--upload&lt;/code&gt;, and &lt;code&gt;--run-command&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://qemu.readthedocs.io/en/master/tools/qemu-img.html" rel="noopener noreferrer"&gt;qemu-img&lt;/a&gt; &lt;code&gt;info&lt;/code&gt;, &lt;code&gt;convert&lt;/code&gt;, and &lt;code&gt;create -b&lt;/code&gt; / &lt;code&gt;-F&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://manpages.ubuntu.com/manpages/resolute/man1/virt-install.1.html" rel="noopener noreferrer"&gt;virt-install(1)&lt;/a&gt; &lt;code&gt;--import&lt;/code&gt; and &lt;code&gt;--os-variant&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://gitlab.com/libosinfo/osinfo-db/-/commit/6f01a968803a30c7e5da631b0205c5982b20b842" rel="noopener noreferrer"&gt;osinfo-db Ubuntu 26.04 entry&lt;/a&gt;, short id &lt;code&gt;ubuntu26.04&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.freedesktop.org/software/systemd/man/latest/machine-id.html" rel="noopener noreferrer"&gt;machine-id(5)&lt;/a&gt; on how an empty &lt;code&gt;/etc/machine-id&lt;/code&gt; is replaced at boot&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>linux</category>
      <category>devops</category>
      <category>virtualization</category>
      <category>kvm</category>
    </item>
    <item>
      <title>Self-Hosted S3 Alternatives in 2026 (RustFS, Garage, SeaweedFS, Ceph)</title>
      <dc:creator>Rost</dc:creator>
      <pubDate>Thu, 08 Oct 2026 08:35:20 +0000</pubDate>
      <link>https://dev.to/rosgluk/self-hosted-s3-alternatives-in-2026-rustfs-garage-seaweedfs-ceph-4klp</link>
      <guid>https://dev.to/rosgluk/self-hosted-s3-alternatives-in-2026-rustfs-garage-seaweedfs-ceph-4klp</guid>
      <description>&lt;p&gt;Amazon S3 has become the de facto API for object storage. After the open source MinIO repository was archived in April 2026, seven credible self-hosted alternatives remain.&lt;/p&gt;

&lt;p&gt;These options are not interchangeable. Some are full object-storage engines that own their data layout; others are thin gateways that expose files you already have. That split — native store versus filesystem-backed gateway — decides more about backups, recovery, and migrations than any feature list does.&lt;/p&gt;

&lt;p&gt;This article compares RustFS, Garage, SeaweedFS, Ceph RGW, VersityGW, S3Proxy, and rclone. Each section covers the storage model, the S3 feature coverage, where the project fits, and the limitation that usually decides the choice. A decision guide for single-server and multi-server deployments closes the article.&lt;/p&gt;

&lt;p&gt;For the broader picture — object storage, databases, search, and AI-native data layers — see the &lt;a href="https://www.glukhov.org/data-infrastructure/" rel="noopener noreferrer"&gt;Data Infrastructure for AI Systems&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-Hosted S3 Storage Comparison
&lt;/h2&gt;

&lt;p&gt;The most important question is whether you need a distributed object store or an S3 interface in front of storage that already exists.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Software&lt;/th&gt;
&lt;th&gt;Storage model&lt;/th&gt;
&lt;th&gt;Distributed&lt;/th&gt;
&lt;th&gt;S3 compatibility&lt;/th&gt;
&lt;th&gt;Plain files on disk&lt;/th&gt;
&lt;th&gt;Complexity&lt;/th&gt;
&lt;th&gt;Best use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;RustFS&lt;/td&gt;
&lt;td&gt;Native object store&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;High, tested subset&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Low to medium&lt;/td&gt;
&lt;td&gt;General MinIO replacement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Garage&lt;/td&gt;
&lt;td&gt;Native object store&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Good, narrower API&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Low to medium&lt;/td&gt;
&lt;td&gt;Small distributed clusters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SeaweedFS&lt;/td&gt;
&lt;td&gt;Distributed blob and file store&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Huge file counts and mixed storage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ceph RGW&lt;/td&gt;
&lt;td&gt;Ceph object gateway&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Large storage infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VersityGW&lt;/td&gt;
&lt;td&gt;S3 gateway over POSIX&lt;/td&gt;
&lt;td&gt;Backend dependent&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;S3 over normal files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S3Proxy&lt;/td&gt;
&lt;td&gt;S3 translation layer&lt;/td&gt;
&lt;td&gt;Backend dependent&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Yes with filesystem-nio2&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Gateway and protocol translation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rclone serve s3&lt;/td&gt;
&lt;td&gt;S3 gateway&lt;/td&gt;
&lt;td&gt;Backend dependent&lt;/td&gt;
&lt;td&gt;Basic, experimental&lt;/td&gt;
&lt;td&gt;Yes with local backend&lt;/td&gt;
&lt;td&gt;Very low&lt;/td&gt;
&lt;td&gt;Simple and lightweight deployments&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;RustFS is the closest direct replacement for a conventional MinIO deployment. Garage fits small distributed clusters. SeaweedFS and Ceph solve broader storage problems. VersityGW can expose an existing POSIX filesystem through S3 while leaving the object payloads as ordinary files. For a feature-by-feature matrix of Garage, MinIO, and AWS S3, see &lt;a href="https://www.glukhov.org/data-infrastructure/object-storage/garage-vs-minio-vs-s3/" rel="noopener noreferrer"&gt;Garage vs MinIO vs AWS S3&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Different Types of S3 Storage
&lt;/h2&gt;

&lt;p&gt;A native object store owns its storage layout:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    A[Application] --&amp;gt;|S3 API| B[Object Store]
    B --&amp;gt; C[Internal Metadata]
    B --&amp;gt; D[Chunks or Object Layout]
    C --&amp;gt; E[Disks]
    D --&amp;gt; E&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;RustFS, Garage, Ceph RGW, and SeaweedFS belong to this family. The files visible on the underlying disks are implementation details, and applications should not manipulate them directly.&lt;/p&gt;

&lt;p&gt;A filesystem-backed S3 gateway has a different relationship with storage:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    A[Application] --&amp;gt;|S3 API| B[S3 Gateway]
    B --&amp;gt; C[POSIX Filesystem]
    C --&amp;gt; D[bucket]
    D --&amp;gt; E[path]
    E --&amp;gt; F[ordinary-file.dat]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;With software such as VersityGW, the filesystem can remain the authoritative representation. An object such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;s3://archive/documents/2026/report.pdf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;can correspond directly to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/storage/archive/documents/2026/report.pdf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  RustFS: The Closest MinIO Replacement
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/rustfs/rustfs" rel="noopener noreferrer"&gt;RustFS&lt;/a&gt; is a distributed object storage system written in Rust and released under the Apache-2.0 license. It provides an S3-oriented operating model familiar to MinIO users, which makes it the first project to evaluate when replacing MinIO.&lt;/p&gt;

&lt;p&gt;Its current S3 implementation covers the operations most applications expect: buckets, objects, multipart uploads, conditional requests, object metadata, tags, versioning, lifecycle management, object lock, presigned URLs, bucket policies, CORS, notifications, replication configuration, and server-side encryption.&lt;/p&gt;

&lt;p&gt;RustFS publishes an &lt;a href="https://docs.rustfs.com/en/reference/s3-compatibility" rel="noopener noreferrer"&gt;S3 compatibility matrix&lt;/a&gt; that is a tested subset, not a claim of complete Amazon S3 coverage. The snapshot verified against commit &lt;code&gt;1e6f5f1e&lt;/code&gt; on 9 August 2026 lists 455 implemented standard tests, 5 lifecycle-behavior tests, 17 unimplemented standard tests, and 270 excluded cases that do not block the compatibility gate. The executable lists under &lt;code&gt;scripts/s3-tests&lt;/code&gt; are the source of truth when those counts move.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where RustFS fits
&lt;/h3&gt;

&lt;p&gt;RustFS is a candidate when an application expects a proper S3 object store.&lt;/p&gt;

&lt;p&gt;Typical uses include application object storage, backup repositories, media storage, AI and data-pipeline artifacts, internal S3 infrastructure, Kubernetes workloads, and replacement of existing MinIO endpoints.&lt;/p&gt;

&lt;p&gt;It works with AWS SDKs and common S3 clients. Applications normally need a custom endpoint, credentials, region settings where required, and path-style addressing.&lt;/p&gt;

&lt;h3&gt;
  
  
  The important limitation
&lt;/h3&gt;

&lt;p&gt;RustFS owns the storage representation. The files below its data directory are RustFS internals.&lt;/p&gt;

&lt;p&gt;That makes RustFS the wrong answer when the requirement is that every S3 object remain an ordinary file you can inspect, copy, &lt;code&gt;rsync&lt;/code&gt;, or recover without the object-storage software. That requirement selects a filesystem-backed gateway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Garage: Distributed S3 Without Ceph-Scale Complexity
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://garagehq.deuxfleurs.fr/" rel="noopener noreferrer"&gt;Garage&lt;/a&gt; is an S3-compatible distributed object store for small and medium self-hosted installations. Deuxfleurs develops it under the AGPL v3 licence and has run it in production since the project's initial releases in 2020.&lt;/p&gt;

&lt;p&gt;Garage is built to run across several ordinary servers, including servers in different physical locations. Replication is part of the design.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A[Applications] --&amp;gt; B[S3 Endpoint]
    B --&amp;gt; C[Garage Node 1]
    B --&amp;gt; D[Garage Node 2]
    B --&amp;gt; E[Garage Node 3]
    C &amp;lt;--&amp;gt; D
    D &amp;lt;--&amp;gt; E
    E &amp;lt;--&amp;gt; C&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Garage is attractive when you have three small servers rather than a rack of dedicated storage hardware. A working setup path, from Docker through cluster layout, replication, and TLS via a reverse proxy, is in the &lt;a href="https://www.glukhov.org/data-infrastructure/object-storage/garage-quickstart/" rel="noopener noreferrer"&gt;Garage quickstart&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where Garage fits
&lt;/h3&gt;

&lt;p&gt;Garage makes sense for homelabs, small hosting platforms, geographically distributed servers, replicated backups, and clusters where Ceph would be excessive.&lt;/p&gt;

&lt;p&gt;Its scope is narrower than AWS S3. Garage's compatibility table leaves out bucket policies, ACLs, and bucket versioning. Choose Garage over RustFS when lightweight multi-node replication is the primary requirement. Choose RustFS when broader S3 behavior and MinIO-like application compatibility matter more.&lt;/p&gt;

&lt;h2&gt;
  
  
  SeaweedFS: More Than an Object Store
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/seaweedfs/seaweedfs" rel="noopener noreferrer"&gt;SeaweedFS&lt;/a&gt; started by storing enormous numbers of files efficiently. It is now a distributed storage platform exposing S3, a FUSE filesystem, WebDAV, SFTP, and HDFS. &lt;a href="https://github.com/seaweedfs/seaweedfs/releases" rel="noopener noreferrer"&gt;SeaweedFS 4.48&lt;/a&gt; was released on 28 September 2026.&lt;/p&gt;

&lt;p&gt;Volume servers pack blobs into append-only volume files. Masters track volumes rather than individual files. A filer provides hierarchical filesystem semantics. The design targets workloads with millions or billions of relatively small objects.&lt;/p&gt;

&lt;h3&gt;
  
  
  S3 support
&lt;/h3&gt;

&lt;p&gt;SeaweedFS documents versioning, object lock, lifecycle rules, tagging, CORS, checksums, presigned URLs, multipart uploads, bucket policies, IAM, STS, and several server-side encryption modes.&lt;/p&gt;

&lt;p&gt;The same cluster can serve S3 table buckets and a built-in Iceberg REST catalog, so Spark, Trino, DuckDB, and similar engines can use Iceberg tables without a separate Hive Metastore or Glue. The project's one-command S3 example listens on port 8333.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where SeaweedFS fits
&lt;/h3&gt;

&lt;p&gt;Consider SeaweedFS for web crawlers and archives, image and document repositories, billions of small objects, data lakes, mixed S3 and filesystem workloads, and storage that grows by adding volume servers.&lt;/p&gt;

&lt;p&gt;There are more components to understand than in a straightforward RustFS installation. SeaweedFS can provide filesystem semantics through its filer and FUSE mount, but objects are stored inside its volume format. It does not satisfy the requirement that every S3 object remain an independent ordinary file on the host filesystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ceph RGW: The Infrastructure-Scale Option
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://ceph.io/" rel="noopener noreferrer"&gt;Ceph&lt;/a&gt; is broader than S3 storage. A cluster can provide block devices through RBD, a filesystem through CephFS, and object storage through RADOS Gateway (RGW). RGW presents an S3-compatible API backed by RADOS.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    A[Applications] --&amp;gt;|S3| B[Ceph RGW]
    B --&amp;gt; C[RADOS]
    C --&amp;gt; D[OSD 1]
    C --&amp;gt; E[OSD 2]
    C --&amp;gt; F[OSD 3]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Ceph supports replication, erasure coding, failure domains, multiple gateways, multisite deployments, access policies, and cluster management tooling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where Ceph fits
&lt;/h3&gt;

&lt;p&gt;Ceph makes sense when object storage is one part of a larger storage platform: Kubernetes persistent volumes, virtual-machine disks, shared filesystems, large S3 repositories, and clusters that need explicit failure domains.&lt;/p&gt;

&lt;p&gt;For a single server with several terabytes, installing Ceph only to obtain an S3 endpoint is difficult to justify. If you already operate Ceph, RGW is the obvious S3 choice. If you do not, RustFS or Garage reaches useful object storage with less machinery.&lt;/p&gt;

&lt;h2&gt;
  
  
  VersityGW: S3 Over Ordinary Files
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/versity/versitygw" rel="noopener noreferrer"&gt;VersityGW&lt;/a&gt; is the option to evaluate when the filesystem itself matters. It is an Apache-2.0 licensed S3 gateway that can expose POSIX filesystems, ScoutFS, Azure Blob Storage, other S3 services, and custom backends.&lt;/p&gt;

&lt;p&gt;Its POSIX backend maps S3 buckets and objects onto directories and files. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;s3://website-assets/images/logo.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;maps to a path such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/storage/website-assets/images/logo.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;logo.png&lt;/code&gt; payload remains a normal file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why plain files are useful
&lt;/h3&gt;

&lt;p&gt;You can inspect storage with ordinary Unix tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find /storage &lt;span class="nt"&gt;-type&lt;/span&gt; f
&lt;span class="nb"&gt;du&lt;/span&gt; &lt;span class="nt"&gt;-sh&lt;/span&gt; /storage/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can read an object without the S3 server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /storage/archive/2026/source.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Conventional filesystem backup tools apply directly: &lt;code&gt;rsync&lt;/code&gt;, &lt;code&gt;restic&lt;/code&gt;, &lt;code&gt;borg&lt;/code&gt;, &lt;code&gt;tar&lt;/code&gt;, and &lt;code&gt;zfs send&lt;/code&gt;. If the gateway software disappears, the payloads are still files in a directory tree.&lt;/p&gt;

&lt;h3&gt;
  
  
  S3 metadata on a POSIX backend
&lt;/h3&gt;

&lt;p&gt;S3 metadata does not fit in a POSIX file by itself. VersityGW stores it in extended attributes by default. Since March 2026, object metadata is a single &lt;code&gt;user.metadata&lt;/code&gt; xattr holding JSON, because one xattr per key hit filesystem key-length limits. A &lt;code&gt;versitygw utils convert-xattr-metadata&lt;/code&gt; command rewrites older per-key attributes. Reads still fall back to the old layout when &lt;code&gt;user.metadata&lt;/code&gt; is absent.&lt;/p&gt;

&lt;p&gt;Filesystems without xattrs can use &lt;code&gt;--sidecar&lt;/code&gt; (or &lt;code&gt;VGW_META_SIDECAR&lt;/code&gt;) to keep metadata in a separate directory tree. The project marks sidecar and &lt;code&gt;--nometa&lt;/code&gt; as less tested than xattrs. &lt;code&gt;--nometa&lt;/code&gt; disables metadata storage and is aimed at read-only hosting of a dataset that already exists; bucket policies and ACLs are metadata, so they are unavailable in that mode.&lt;/p&gt;

&lt;p&gt;Object versions can live in a separate directory. The project's POSIX example passes &lt;code&gt;--versioning-dir&lt;/code&gt; for older versions while the current payload stays at its natural path.&lt;/p&gt;

&lt;p&gt;Changing a file behind the gateway can leave S3 metadata, including ETags, inconsistent with the new bytes. A workable operating model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application access:
    Read through S3
    Write through S3

Administrator access:
    Read the filesystem directly
    Inspect the filesystem directly
    Back up the filesystem directly
    Avoid modifying objects behind the gateway
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;VersityGW 1.8.0, already the version a September 2026 COSI driver release targeted, adds a standalone IAM service (&lt;code&gt;versitygw iam&lt;/code&gt;), STS &lt;code&gt;AssumeRoleWithWebIdentity&lt;/code&gt;, and IAM policy conditions.&lt;/p&gt;

&lt;h2&gt;
  
  
  S3Proxy: A General Translation Layer
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/gaul/s3proxy" rel="noopener noreferrer"&gt;S3Proxy&lt;/a&gt; implements the S3 API and translates requests onto several backends: local files, Azure Blob Storage, Google Cloud Storage, OpenStack Swift, SFTP, and other S3 services. It requires Java 17 or newer.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    A[S3 client] --&amp;gt; B[S3Proxy]
    B --&amp;gt; C[filesystem-nio2]
    C --&amp;gt; D[Local filesystem]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The recommended on-disk provider is &lt;code&gt;filesystem-nio2&lt;/code&gt;. The older &lt;code&gt;filesystem&lt;/code&gt; provider is deprecated. Object keys become filesystem paths, and metadata uses user extended attributes, so the payloads stay ordinary files.&lt;/p&gt;

&lt;p&gt;The filesystem backend refuses object versioning and server-side encryption. Versioning stays unsupported (&lt;code&gt;supportsVersioning()&lt;/code&gt; is false, and those requests remain HTTP 501) because a version layout written to a real disk would become a format the project would have to keep. Server-side encryption is refused for the same class of reason: reporting AES256 over a plaintext file someone can read would be a false claim. The in-memory &lt;code&gt;transient-nio2&lt;/code&gt; backend implements both, and the data dies with the process. The project README also lists bucket policies, object tagging, and ACLs other than &lt;code&gt;private&lt;/code&gt; and &lt;code&gt;public-read&lt;/code&gt; as unsupported.&lt;/p&gt;

&lt;p&gt;S3Proxy is the tool when you need that translation across backends. For a straightforward S3-to-POSIX gateway, VersityGW is the smaller surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  rclone serve s3: Lightweight but Experimental
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://rclone.org/commands/rclone_serve_s3/" rel="noopener noreferrer"&gt;rclone &lt;code&gt;serve s3&lt;/code&gt;&lt;/a&gt; exposes an rclone backend through an S3-compatible API. The documentation still labels the command experimental.&lt;/p&gt;

&lt;p&gt;For a local filesystem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rclone serve s3 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--addr&lt;/span&gt; :9000 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--auth-key&lt;/span&gt; ACCESS_KEY,SECRET_KEY &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;local&lt;/span&gt;:/srv/s3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without &lt;code&gt;--addr&lt;/code&gt;, the server binds &lt;code&gt;127.0.0.1:8080&lt;/code&gt;. &lt;code&gt;--auth-key&lt;/code&gt; enables Signature Version 4. Multipart uploads are written in part-number order to a temporary object and renamed into place on completion, so a failed upload does not replace an existing object.&lt;/p&gt;

&lt;p&gt;Objects written through a &lt;code&gt;local&lt;/code&gt; backend become files under that path. The same command can sit in front of other rclone remotes. It is a reasonable choice for internal tooling, a lab, or a temporary migration hop. It is a weak permanent S3 layer for many unrelated production applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  What About MinIO?
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/minio/minio" rel="noopener noreferrer"&gt;MinIO GitHub repository&lt;/a&gt; was archived on 13 February 2026, briefly unarchived, and archived again on 25 April 2026. It is read-only. The repository notice points operators at AIStor, MinIO's commercial distribution, and states that the community edition is source-only.&lt;/p&gt;

&lt;p&gt;An existing MinIO deployment can keep running, particularly when it is isolated and its behavior is already understood. The full timeline, the operational risk, and a phased migration plan are in &lt;a href="https://www.glukhov.org/data-infrastructure/object-storage/minio-dead/" rel="noopener noreferrer"&gt;MinIO CE in 2026&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For the S3 behavior of the system you may be leaving, &lt;a href="https://www.glukhov.org/data-infrastructure/object-storage/minio-vs-aws-s3/" rel="noopener noreferrer"&gt;MinIO as an AWS S3 alternative&lt;/a&gt; documents how MinIO maps onto S3. The &lt;a href="https://www.glukhov.org/data-infrastructure/object-storage/minio-cheatsheet/" rel="noopener noreferrer"&gt;MinIO command line cheatsheet&lt;/a&gt; is the audit companion for a cluster that is still up. For a new installation, an actively maintained alternative is the default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plain Files vs Native Object Storage
&lt;/h2&gt;

&lt;p&gt;Use RustFS, Garage, SeaweedFS, or Ceph when the object store should control durability, metadata, distribution, and layout. That buys richer S3 semantics, replication or erasure coding, horizontal scaling, and versioning where the project implements it. The cost is dependence on the engine's on-disk representation.&lt;/p&gt;

&lt;p&gt;Use VersityGW, S3Proxy, or rclone when an existing filesystem should remain authoritative. That buys plain recoverable files, conventional backups, direct inspection, and compatibility with ZFS, XFS, ext4, and NFS. Distributed durability then has to come from the filesystem or the storage array, not from the S3 process. On a single server or a NAS, that trade is often the right one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing the Right Self-Hosted S3 Server
&lt;/h2&gt;

&lt;p&gt;For a single server that should resemble MinIO, start with RustFS.&lt;/p&gt;

&lt;p&gt;For three or more modest servers where replication is the main requirement, and the application can live without bucket policies and versioning, choose Garage.&lt;/p&gt;

&lt;p&gt;For enormous numbers of small objects, mixed filesystem access, or a data-lake workload that may grow into Iceberg tables, consider SeaweedFS.&lt;/p&gt;

&lt;p&gt;When the same cluster must also provide block devices and a distributed filesystem, choose Ceph RGW.&lt;/p&gt;

&lt;p&gt;When the requirement is specifically an S3 API over ordinary files — a NAS, an archive, a document repository, a crawler, or a backup target — start with VersityGW.&lt;/p&gt;

&lt;p&gt;When the job is translating S3 onto Azure, GCS, Swift, SFTP, or another S3 service, use S3Proxy.&lt;/p&gt;

&lt;p&gt;When the deployment is small, temporary, and can tolerate an experimental server, &lt;code&gt;rclone serve s3&lt;/code&gt; may be enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommended Architecture for a Single Storage Server
&lt;/h2&gt;

&lt;p&gt;If applications should reach the data only through S3, RustFS owns the object layer and the filesystem is an implementation detail:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    A[Applications] --&amp;gt;|S3| B[RustFS]
    B --&amp;gt; C[Local Storage]
    C --&amp;gt; D[Filesystem or RAID]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;If the files themselves must survive the gateway, keep the data in a POSIX tree:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    A[Applications] --&amp;gt;|S3| B[VersityGW]
    B --&amp;gt; C[POSIX Filesystem]
    C --&amp;gt; D[ext4, XFS, ZFS, NFS]
    D --&amp;gt; E[Plain Files]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;In the second design, redundancy is a ZFS mirror, RAID, snapshots, filesystem replication, or a conventional backup tool. The S3 service can be replaced without rewriting the data format.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Maintained self-hosted S3 still exists after MinIO's public repository went read-only. The choice that matters is whether S3 owns the bytes or only fronts files that already exist. Every comparison above follows from that answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/rustfs/rustfs" rel="noopener noreferrer"&gt;RustFS on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.rustfs.com/en/reference/s3-compatibility" rel="noopener noreferrer"&gt;RustFS S3 compatibility matrix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://garagehq.deuxfleurs.fr/" rel="noopener noreferrer"&gt;Garage project site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/seaweedfs/seaweedfs" rel="noopener noreferrer"&gt;SeaweedFS on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/seaweedfs/seaweedfs/releases" rel="noopener noreferrer"&gt;SeaweedFS releases&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ceph.io/" rel="noopener noreferrer"&gt;Ceph project site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/versity/versitygw" rel="noopener noreferrer"&gt;VersityGW on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/versity/versitygw/wiki/POSIX-metadata" rel="noopener noreferrer"&gt;VersityGW POSIX metadata&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gaul/s3proxy" rel="noopener noreferrer"&gt;S3Proxy on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rclone.org/commands/rclone_serve_s3/" rel="noopener noreferrer"&gt;rclone serve s3 documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/minio/minio" rel="noopener noreferrer"&gt;MinIO on GitHub (archived)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>s3</category>
      <category>minio</category>
      <category>selfhosting</category>
    </item>
    <item>
      <title>Go Web Frameworks in 2026: net/http, chi, Gin, Echo, Fiber Compared</title>
      <dc:creator>Rost</dc:creator>
      <pubDate>Mon, 05 Oct 2026 23:53:52 +0000</pubDate>
      <link>https://dev.to/rosgluk/go-web-frameworks-in-2026-nethttp-chi-gin-echo-fiber-compared-bm3</link>
      <guid>https://dev.to/rosgluk/go-web-frameworks-in-2026-nethttp-chi-gin-echo-fiber-compared-bm3</guid>
      <description>&lt;p&gt;Go ships a production-capable HTTP server in its standard library, and since Go 1.22 the built-in router handles method matching and path parameters on its own. The framework question has narrowed, not disappeared.&lt;/p&gt;

&lt;p&gt;Five stacks cover almost every Go backend decision in 2026: the standard library's &lt;code&gt;net/http&lt;/code&gt;, chi, Gin, Echo, and Fiber. Each takes a different position on the same tradeoff — what the library gives you versus what you owe it in dependencies, conventions, and ecosystem compatibility. This article compares them on routing power, handler ergonomics, middleware, and measured performance, then ends with a decision table rather than a single verdict.&lt;/p&gt;

&lt;p&gt;If you are starting from zero and want the full build process around your choice — validation, authentication, testing, deployment — the step-by-step version lives in &lt;a href="https://www.glukhov.org/app-architecture/api-architecture/implementing-api-in-go/" rel="noopener noreferrer"&gt;Building REST APIs in Go&lt;/a&gt;. Here the focus stays on the stacks themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  The post-1.22 ServeMux reset
&lt;/h2&gt;

&lt;p&gt;The reason the framework debate changed shape is one addition to the standard library. Go 1.22 gave &lt;code&gt;http.ServeMux&lt;/code&gt; method-aware patterns and path parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;mux&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewServeMux&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GET /users/{id}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PathValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"POST /users"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;createUser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before this, any API needing &lt;code&gt;GET /users/{id}&lt;/code&gt; required a third-party router, which is how Gin, chi, and Echo became defaults. In 2026, the stdlib covers static routes, method matching, single-segment and wildcard parameters, and middleware via plain function wrapping. What it still does not give you: route groups, composable middleware chains, struct binding, or per-route param types.&lt;/p&gt;

&lt;h2&gt;
  
  
  The contenders
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;net/http (stdlib)&lt;/th&gt;
&lt;th&gt;chi v5.3.2&lt;/th&gt;
&lt;th&gt;Gin v1.12.0&lt;/th&gt;
&lt;th&gt;Echo v4.16.0&lt;/th&gt;
&lt;th&gt;Fiber v2.52.15&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HTTP engine&lt;/td&gt;
&lt;td&gt;net/http&lt;/td&gt;
&lt;td&gt;net/http&lt;/td&gt;
&lt;td&gt;net/http&lt;/td&gt;
&lt;td&gt;net/http&lt;/td&gt;
&lt;td&gt;fasthttp&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Handler signature&lt;/td&gt;
&lt;td&gt;&lt;code&gt;func(w, r)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;func(w, r)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;func(c *gin.Context)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;func(c echo.Context) error&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;func(c *fiber.Ctx) error&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;http.Handler compatible&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Middleware ecosystem&lt;/td&gt;
&lt;td&gt;growing, stdlib-shaped&lt;/td&gt;
&lt;td&gt;stdlib-shaped&lt;/td&gt;
&lt;td&gt;largest, Gin-specific&lt;/td&gt;
&lt;td&gt;large, Echo-specific&lt;/td&gt;
&lt;td&gt;growing, fasthttp-specific&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Binding/validation&lt;/td&gt;
&lt;td&gt;manual&lt;/td&gt;
&lt;td&gt;manual&lt;/td&gt;
&lt;td&gt;built-in tags&lt;/td&gt;
&lt;td&gt;built-in&lt;/td&gt;
&lt;td&gt;via helpers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP/2&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependencies&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;~6&lt;/td&gt;
&lt;td&gt;~8&lt;/td&gt;
&lt;td&gt;~4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All five use trie- or radix-style routers with path parameters and grouped routes. The structural divide is the last row of the handler-signature story: chi and stdlib speak plain &lt;code&gt;http.Handler&lt;/code&gt;; Gin, Echo, and Fiber wrap requests in their own context type. Fiber is the outlier once more because it sits on fasthttp, not &lt;code&gt;net/http&lt;/code&gt; at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I benchmarked
&lt;/h2&gt;

&lt;p&gt;Rather than quoting synthetic router benchmarks, I ran the same service on all five stacks. The rig:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Go 1.27.1&lt;/strong&gt;, Gin v1.12.0, chi v5.3.2, Echo v4.16.0, Fiber v2.52.15&lt;/li&gt;
&lt;li&gt;Intel Core Ultra 7 265, Linux, 20 threads, localhost, no TLS&lt;/li&gt;
&lt;li&gt;Three routes per stack: &lt;code&gt;GET /health&lt;/code&gt;, &lt;code&gt;GET /users/{id}&lt;/code&gt;, &lt;code&gt;GET /api/v1/users/{uid}/orders/{oid}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Load: 8 keep-alive workers for 3 seconds against the two-parameter route, after a warmup pass&lt;/li&gt;
&lt;li&gt;Every handler marshals a small JSON document per request with &lt;code&gt;encoding/json&lt;/code&gt; — so the numbers include serialization, not just routing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The stdlib version of the hot route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GET /api/v1/users/{uid}/orders/{oid}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PathValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"uid"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PathValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"oid"&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;and the Fiber equivalent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/v1/users/:uid/orders/:oid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Params&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"uid"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Params&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"oid"&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;Two honesty notes before the numbers. First, this is a localhost micro-workload: no database, no TLS handshake, no middleware chain. Real services bottleneck on I/O — the framework's share of a request that queries PostgreSQL via &lt;a href="https://www.glukhov.org/app-architecture/data-access/comparing-go-orms-gorm-ent-bun-sqlc/" rel="noopener noreferrer"&gt;GORM, Ent, Bun, or sqlc&lt;/a&gt; is a rounding error. Second, each stack got its own server process and an identical handler shape, so the comparison is between stacks, not between hand-tuned configurations.&lt;/p&gt;

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

&lt;p&gt;Throughput on the dynamic two-parameter route:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stack&lt;/th&gt;
&lt;th&gt;Requests/sec&lt;/th&gt;
&lt;th&gt;Avg latency&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fiber (fasthttp)&lt;/td&gt;
&lt;td&gt;205,958&lt;/td&gt;
&lt;td&gt;0.034 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Echo&lt;/td&gt;
&lt;td&gt;169,826&lt;/td&gt;
&lt;td&gt;0.042 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gin&lt;/td&gt;
&lt;td&gt;169,715&lt;/td&gt;
&lt;td&gt;0.042 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;net/http ServeMux&lt;/td&gt;
&lt;td&gt;169,708&lt;/td&gt;
&lt;td&gt;0.042 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;chi&lt;/td&gt;
&lt;td&gt;163,017&lt;/td&gt;
&lt;td&gt;0.044 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The four &lt;code&gt;net/http&lt;/code&gt;-based stacks land within 4% of each other. Fiber's fasthttp engine is about 21% ahead on this localhost workload — real, but the kind of gap that a single database roundtrip (0.5–5 ms) makes irrelevant in production.&lt;/p&gt;

&lt;p&gt;To see where the per-request cost actually goes, I also benchmarked handler invocation without the network, calling each router directly through &lt;code&gt;http.Handler.ServeHTTP&lt;/code&gt; (Fiber through its fasthttp handler):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stack&lt;/th&gt;
&lt;th&gt;Time/op&lt;/th&gt;
&lt;th&gt;Bytes/op&lt;/th&gt;
&lt;th&gt;Allocs/op&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gin&lt;/td&gt;
&lt;td&gt;289 ns&lt;/td&gt;
&lt;td&gt;192 B&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Echo&lt;/td&gt;
&lt;td&gt;333 ns&lt;/td&gt;
&lt;td&gt;192 B&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;net/http ServeMux&lt;/td&gt;
&lt;td&gt;430 ns&lt;/td&gt;
&lt;td&gt;240 B&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fiber&lt;/td&gt;
&lt;td&gt;541 ns*&lt;/td&gt;
&lt;td&gt;192 B&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;chi&lt;/td&gt;
&lt;td&gt;592 ns&lt;/td&gt;
&lt;td&gt;897 B&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;* The Fiber figure carries harness overhead — the test has to copy the request into fasthttp's reusable context each iteration, which the real server avoids by reading from the wire. Treat it as an upper bound; the server-level throughput above is the fair comparison for Fiber.&lt;/p&gt;

&lt;p&gt;The allocation profile is the more durable story. Gin and Echo pool their request contexts, so a request through them costs 3 allocations. The stdlib's 5 allocations go to the request-scoped plumbing itself. chi stores route parameters in the request &lt;code&gt;context.Context&lt;/code&gt;, which shows up as 897 B and 7 allocations per request — the price of staying a plain &lt;code&gt;http.Handler&lt;/code&gt; with rich route info.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fiber and the fasthttp tradeoff
&lt;/h2&gt;

&lt;p&gt;Fiber is the fastest stack on the server benchmark, and the speed comes with a compatibility bill. fasthttp is a from-scratch HTTP implementation with its own request/response types, so:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;net/http&lt;/code&gt; middleware does not attach.&lt;/strong&gt; Any library expecting an &lt;code&gt;http.Handler&lt;/code&gt; or &lt;code&gt;*http.Request&lt;/code&gt; needs a conversion or a Fiber-native port.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No HTTP/2.&lt;/strong&gt; fasthttp does not implement it; Go's &lt;code&gt;net/http&lt;/code&gt; gets h2 from the standard library for free over TLS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WebSocket, streaming, and &lt;code&gt;http.Flusher&lt;/code&gt;-style tricks&lt;/strong&gt; all take the fasthttp path, which differs from the stdlib behavior most Go documentation assumes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What Fiber buys in exchange: the fastest numbers in the table above, an Express-like API, and lower allocations than the framework benches suggest once the wire parsing advantage is included. It fits when responses are small, routing dominates, HTTP/2 is terminated upstream, and the middleware you need exists in the Fiber ecosystem. Those conditions hold for some edge services and hold for almost nothing else.&lt;/p&gt;

&lt;h2&gt;
  
  
  chi: the boring middle path
&lt;/h2&gt;

&lt;p&gt;chi is a router and a middleware pipeline, nothing more. Handlers are &lt;code&gt;func(w, r)&lt;/code&gt;, middleware is &lt;code&gt;func(http.Handler) http.Handler&lt;/code&gt;, and everything written for the stdlib attaches without adapters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;chi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRouter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RequestID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RealIP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Recoverer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="n"&gt;chi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/users/{uid}/orders/{oid}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;getOrder&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;That is the whole pitch: route groups and a middleware stack on top of the standard interface. The allocation table prices it honestly — richer route context per request than the stdlib alone, in exchange for composable middleware and nested routes. For services that want structure without a framework identity, chi plus &lt;code&gt;net/http&lt;/code&gt; is the configuration most Go teams converge on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gin vs Echo, in code
&lt;/h2&gt;

&lt;p&gt;Gin and Echo are near-twins on performance — within noise in both benchmarks — so the choice between them is API taste and error-handling philosophy.&lt;/p&gt;

&lt;p&gt;Echo centralizes errors in an &lt;code&gt;HTTPErrorHandler&lt;/code&gt;. Handlers return &lt;code&gt;error&lt;/code&gt;, and one function maps errors to responses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;getOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"uid"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"oid"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewHTTPError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusNotFound&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"order not found"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;o&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;Gin handlers return nothing; errors accumulate on the context and you render them where you choose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;getOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"uid"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"oid"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusNotFound&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"order not found"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;o&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;Echo's model maps cleanly onto &lt;a href="https://www.glukhov.org/app-architecture/code-architecture/go-error-handling-architecture/" rel="noopener noreferrer"&gt;Go error handling architecture&lt;/a&gt; with wrapped errors and centralized boundary handling — one place translates &lt;code&gt;errors.Is&lt;/code&gt; results into status codes. Gin's model matches the more common pattern of deciding per handler. Beyond errors, Gin's deciding advantages are ecosystem size and built-in struct binding with validation tags; Echo's are a consistent context API and stronger built-in middleware. Both integrate Swagger generation through their own adapters — the per-framework setup is covered in &lt;a href="https://www.glukhov.org/app-architecture/api-architecture/adding-swagger-to-api-in-go/" rel="noopener noreferrer"&gt;Adding Swagger to Your Go API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One more axis: context propagation. Gin and Echo wrap &lt;code&gt;context.Context&lt;/code&gt; inside their own context types, so passing cancellation into handlers requires an extraction step. The patterns for keeping cancellation, timeouts, and values straight across any of these wrappers are in &lt;a href="https://www.glukhov.org/app-architecture/code-architecture/go-context-cancellation-timeouts/" rel="noopener noreferrer"&gt;Go context.Context Done Right&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The timeouts nobody sets
&lt;/h2&gt;

&lt;p&gt;Every stack in this comparison leaves server timeouts at zero defaults unless you set them — and zero means no limit. A client that opens a connection and sends nothing holds a goroutine and a file descriptor indefinitely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;srv&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Addr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;              &lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;           &lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ReadHeaderTimeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ReadTimeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;WriteTimeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;IdleTimeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="m"&gt;120&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;srv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ReadHeaderTimeout&lt;/code&gt; is the one to set first — it bounds slowloris-style connection exhaustion without breaking long uploads the way a blanket &lt;code&gt;ReadTimeout&lt;/code&gt; can. &lt;code&gt;WriteTimeout&lt;/code&gt; must cover the slowest legitimate response, which makes it a policy decision tied to your timeout budgets rather than a copy-paste constant. The same discipline applies per framework: Gin, Echo, and Fiber all accept server-level timeout configuration, and none of them enable it by default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing
&lt;/h2&gt;



&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    A[New Go HTTP service] --&amp;gt; B{Internal tool, plugin,&amp;lt;br/&amp;gt;or zero-dependency requirement?}
    B -- yes --&amp;gt; C[net/http ServeMux]
    B -- no --&amp;gt; D{Want built-in binding,&amp;lt;br/&amp;gt;validation, big middleware catalog?}
    D -- no --&amp;gt; E[chi + net/http]
    D -- yes --&amp;gt; F{Team convention?}
    F -- error-returning handlers --&amp;gt; G[Echo]
    F -- largest ecosystem, Gin idioms --&amp;gt; H[Gin]
    A --&amp;gt; I{Small responses, extreme throughput,&amp;lt;br/&amp;gt;no HTTP/2, upstream TLS termination?}
    I -- all true --&amp;gt; J[Fiber]
    I -- no --&amp;gt; E&lt;/code&gt;&lt;/pre&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Pick&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Internal service, CLI endpoint, plugin, library&lt;/td&gt;
&lt;td&gt;net/http alone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Most APIs — structure without framework lock-in&lt;/td&gt;
&lt;td&gt;chi on top of net/http&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Public API, larger team, validation and binding out of the box&lt;/td&gt;
&lt;td&gt;Gin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Public API, centralized error translation, consistent context API&lt;/td&gt;
&lt;td&gt;Echo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Proven routing/serialization bottleneck, tiny JSON, no HTTP/2&lt;/td&gt;
&lt;td&gt;Fiber&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If the honest default for a new service in 2026 has to be named: start with &lt;code&gt;net/http&lt;/code&gt;, add chi when middleware or route groups start to ache, and move to Gin or Echo only when binding, validation, or team convention justifies a framework identity. Fiber stays a specialist tool — worth benchmarking against when throughput is proven to be the problem, priced against the compatibility bill it charges.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating between stacks
&lt;/h2&gt;

&lt;p&gt;Migration cost tracks the handler-signature divide. stdlib ↔ chi is mechanical: handlers keep the same signature, routes move from &lt;code&gt;mux.HandleFunc&lt;/code&gt; patterns to &lt;code&gt;r.Get&lt;/code&gt;, middleware attaches to the chi stack. Gin ↔ Echo is a context-type translation — &lt;code&gt;c.Param&lt;/code&gt;, &lt;code&gt;c.JSON&lt;/code&gt;, and middleware logic port nearly line-for-line. Anything moving to or from Fiber is a rewrite of the HTTP layer, because &lt;code&gt;*http.Request&lt;/code&gt;-based code, &lt;code&gt;net/http&lt;/code&gt; middleware, and h2 assumptions do not survive the fasthttp boundary; the least painful route is keeping business logic in framework-free packages and regenerating the transport layer, which is also the structure that makes the next migration cheap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Useful links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.glukhov.org/app-architecture/" rel="noopener noreferrer"&gt;App Architecture hub — API design, code structure, and integration patterns&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.glukhov.org/developer-tools/comparisons/most-popular-go-projects-on-github/" rel="noopener noreferrer"&gt;Top Trending Go Projects on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>api</category>
      <category>architecture</category>
      <category>dev</category>
      <category>go</category>
    </item>
    <item>
      <title>Best Newsletter Platforms in 2026: Pricing, Hosting and SEO</title>
      <dc:creator>Rost</dc:creator>
      <pubDate>Mon, 05 Oct 2026 10:38:35 +0000</pubDate>
      <link>https://dev.to/rosgluk/best-newsletter-platforms-in-2026-pricing-hosting-and-seo-46ka</link>
      <guid>https://dev.to/rosgluk/best-newsletter-platforms-in-2026-pricing-hosting-and-seo-46ka</guid>
      <description>&lt;p&gt;Newsletter platforms split into thin email senders and full publishing systems with archives and memberships. That split decides hosting, SEO, and migration more than any editor does.&lt;/p&gt;

&lt;p&gt;This comparison covers Buttondown, EmailOctopus, beehiiv, Kit, Ghost, Substack, MailerLite, Brevo, and Sender. Prices are in USD, reflect the public pricing in October 2026, and normally rise as the mailing list grows on subscriber-based plans.&lt;/p&gt;

&lt;p&gt;Each platform section describes what the product actually does, its pricing tiers, and where the public archive lives. The closing sections cover the three archive ownership models, canonical URLs, sender authentication, and API publishing, because those decisions outlast any single vendor choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Platform models: email-first, publishing-first, hybrid
&lt;/h2&gt;

&lt;p&gt;Email-first services assume that another system owns the website and the content. Publishing-first services want the newsletter platform itself to become an important public destination. A few deliberately sit between those two models, and the architectural choice carries consequences for SEO, archives, and migration.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    A[Newsletter platforms] --&amp;gt; B[Email-first]
    A --&amp;gt; C[Publishing-first]
    A --&amp;gt; D[Hybrid]

    B --&amp;gt; B1[EmailOctopus]
    B --&amp;gt; B2[Brevo]
    B --&amp;gt; B3[Sender]
    B --&amp;gt; B4[MailerLite]

    C --&amp;gt; C1[Substack]
    C --&amp;gt; C2[beehiiv]
    C --&amp;gt; C3[Ghost]

    D --&amp;gt; D1[Buttondown]
    D --&amp;gt; D2[Kit]&lt;/code&gt;&lt;/pre&gt;



&lt;h2&gt;
  
  
  Pricing and features at a glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Free plan&lt;/th&gt;
&lt;th&gt;Paid entry point&lt;/th&gt;
&lt;th&gt;Public archive&lt;/th&gt;
&lt;th&gt;External canonical support&lt;/th&gt;
&lt;th&gt;API&lt;/th&gt;
&lt;th&gt;Main model&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Buttondown&lt;/td&gt;
&lt;td&gt;100 subscribers&lt;/td&gt;
&lt;td&gt;From $9/mo&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Hybrid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EmailOctopus&lt;/td&gt;
&lt;td&gt;2,500 subscribers, 10,000 emails/mo&lt;/td&gt;
&lt;td&gt;$10/mo, or $9/mo annual&lt;/td&gt;
&lt;td&gt;Individual web copies&lt;/td&gt;
&lt;td&gt;No documented per-issue override&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Email-first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;beehiiv&lt;/td&gt;
&lt;td&gt;2,500 subscribers, unlimited sends&lt;/td&gt;
&lt;td&gt;From $43/mo annual&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Limited for external-site workflows&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Publishing-first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kit&lt;/td&gt;
&lt;td&gt;10,000 subscribers, unlimited broadcasts&lt;/td&gt;
&lt;td&gt;From $33/mo annual&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Hybrid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ghost(Pro)&lt;/td&gt;
&lt;td&gt;No permanent free hosted plan&lt;/td&gt;
&lt;td&gt;From $18/mo annual&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Publishing-first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Substack&lt;/td&gt;
&lt;td&gt;Unlimited free publishing&lt;/td&gt;
&lt;td&gt;10% of paid revenue&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Publishing-first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MailerLite&lt;/td&gt;
&lt;td&gt;250 subscribers, 2,500 emails/mo&lt;/td&gt;
&lt;td&gt;From $12/mo&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Email-first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Brevo&lt;/td&gt;
&lt;td&gt;300 sends/day&lt;/td&gt;
&lt;td&gt;From $9/mo&lt;/td&gt;
&lt;td&gt;Web campaign copies&lt;/td&gt;
&lt;td&gt;No comparable publication archive&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Email-first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sender&lt;/td&gt;
&lt;td&gt;2,500 subscribers, 15,000 emails/mo&lt;/td&gt;
&lt;td&gt;From $7/mo annual&lt;/td&gt;
&lt;td&gt;Web campaign copies&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Email-first&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The free allowance is a useful starting point, but it should not dominate the decision. A newsletter with 500 subscribers may be operationally better served by a $10 email platform than by a free system that also wants to become the primary CMS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Buttondown
&lt;/h2&gt;

&lt;p&gt;Buttondown stays focused on email while adding publishing features: Markdown support, custom sending domains, a hosted archive, an API, and external canonical URLs for individual issues. A newsletter can therefore keep a public Buttondown presence while the permanent version lives on the publisher's own site.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A[Canonical website issue] --&amp;gt; B[Buttondown campaign]
    B --&amp;gt; C[Subscribers]
    B --&amp;gt; D[Buttondown archive]
    D --&amp;gt; A&lt;/code&gt;&lt;/pre&gt;



&lt;h3&gt;
  
  
  Buttondown pricing
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan or tier&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Limits / notable features&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;First 100 subscribers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Up to 1,000 subscribers&lt;/td&gt;
&lt;td&gt;$9/mo&lt;/td&gt;
&lt;td&gt;Core newsletter features&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Around 5,000 subscribers&lt;/td&gt;
&lt;td&gt;$29/mo&lt;/td&gt;
&lt;td&gt;Subscriber-based pricing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Around 10,000 subscribers&lt;/td&gt;
&lt;td&gt;$79/mo&lt;/td&gt;
&lt;td&gt;Subscriber-based pricing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Around 20,000 subscribers&lt;/td&gt;
&lt;td&gt;$139/mo&lt;/td&gt;
&lt;td&gt;Subscriber-based pricing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tagging and segmentation&lt;/td&gt;
&lt;td&gt;+$9/mo&lt;/td&gt;
&lt;td&gt;Optional add-on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Analytics&lt;/td&gt;
&lt;td&gt;+$9/mo&lt;/td&gt;
&lt;td&gt;Optional add-on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RSS-to-email&lt;/td&gt;
&lt;td&gt;+$9/mo&lt;/td&gt;
&lt;td&gt;Optional add-on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Paid subscriptions&lt;/td&gt;
&lt;td&gt;+$9/mo&lt;/td&gt;
&lt;td&gt;Optional add-on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multiple newsletters&lt;/td&gt;
&lt;td&gt;+$29/mo&lt;/td&gt;
&lt;td&gt;Optional add-on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automations&lt;/td&gt;
&lt;td&gt;+$29/mo&lt;/td&gt;
&lt;td&gt;Optional add-on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom-domain archive&lt;/td&gt;
&lt;td&gt;+$29/mo&lt;/td&gt;
&lt;td&gt;Optional add-on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Whitelabeling&lt;/td&gt;
&lt;td&gt;+$79/mo&lt;/td&gt;
&lt;td&gt;Optional add-on&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The modular pricing keeps a simple newsletter inexpensive, but stacking several add-ons raises the monthly cost quickly. Buttondown fits publishers whose website remains the authoritative copy and who want distribution plus a secondary public archive rather than a replacement for the website.&lt;/p&gt;

&lt;h2&gt;
  
  
  EmailOctopus
&lt;/h2&gt;

&lt;p&gt;EmailOctopus takes the conventional email-marketing approach: subscriber management, forms, campaigns, templates, automations, analytics, and API access, without trying to become a publishing destination. For a website that already has its own CMS or static-site generator, that is usually exactly what is required.&lt;/p&gt;

&lt;p&gt;Each sent campaign receives a public web version, but EmailOctopus does not automatically build a publication-style archive containing every issue. Its normal model is for the publisher to create the archive page separately.&lt;/p&gt;

&lt;h3&gt;
  
  
  EmailOctopus pricing
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Subscribers&lt;/th&gt;
&lt;th&gt;Email allowance&lt;/th&gt;
&lt;th&gt;Notable differences&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Starter&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;2,500&lt;/td&gt;
&lt;td&gt;10,000/mo&lt;/td&gt;
&lt;td&gt;EmailOctopus branding, reports retained 30 days, 1 form and 1 landing page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pro monthly&lt;/td&gt;
&lt;td&gt;$10/mo&lt;/td&gt;
&lt;td&gt;Tier-based&lt;/td&gt;
&lt;td&gt;Tier-based&lt;/td&gt;
&lt;td&gt;Branding removed, permanent reports, unlimited forms and landing pages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pro annual&lt;/td&gt;
&lt;td&gt;From $9/mo&lt;/td&gt;
&lt;td&gt;Tier-based&lt;/td&gt;
&lt;td&gt;Tier-based&lt;/td&gt;
&lt;td&gt;Same Pro features, 10% annual discount&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connect Starter&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;2,500&lt;/td&gt;
&lt;td&gt;Unlimited via own SES account&lt;/td&gt;
&lt;td&gt;Amazon SES delivery, EmailOctopus branding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connect Pro&lt;/td&gt;
&lt;td&gt;From $8/mo annual&lt;/td&gt;
&lt;td&gt;Tier-based&lt;/td&gt;
&lt;td&gt;Unlimited via own SES account&lt;/td&gt;
&lt;td&gt;Full design control and permanent reports&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;EmailOctopus Connect is the option for users already operating AWS infrastructure: EmailOctopus manages contacts and campaigns, while Amazon SES performs the actual delivery.&lt;/p&gt;

&lt;p&gt;A sensible website-first archive structure is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;example.com/newsletter/
    2026-08/
    2026-09/
    2026-10/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with EmailOctopus responsible for sending those issues to subscribers. The hosted web copies are useful for "view in browser" links and sharing, but the permanent archive does not need to depend on them.&lt;/p&gt;

&lt;h2&gt;
  
  
  beehiiv
&lt;/h2&gt;

&lt;p&gt;beehiiv treats newsletters as a publishing business rather than an email list. It combines email, a hosted website, public posts, subscription forms, recommendations, analytics, advertisements, paid subscriptions, digital products, and growth tooling, and the free tier already includes a public website with unlimited email sends.&lt;/p&gt;

&lt;p&gt;That positioning makes beehiiv attractive when the newsletter itself is the publication. For an established publication with its own CMS, the same breadth creates overlap with the existing content system.&lt;/p&gt;

&lt;h3&gt;
  
  
  beehiiv pricing
&lt;/h3&gt;

&lt;p&gt;beehiiv was in the middle of a plan-name transition in October 2026, so customers may see the newer Free/Lite/Pro naming or the previous Launch/Scale/Max terminology depending on account and renewal state.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Subscriber capacity&lt;/th&gt;
&lt;th&gt;Notable features&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Launch / Free&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;Up to 2,500&lt;/td&gt;
&lt;td&gt;Unlimited sends, website, custom domain, analytics, recommendations, API access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scale&lt;/td&gt;
&lt;td&gt;From $49/mo monthly or $43/mo annual&lt;/td&gt;
&lt;td&gt;Tier-based, up to 100,000&lt;/td&gt;
&lt;td&gt;Automations, Ad Network, paid subscriptions, digital products, advanced analytics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max&lt;/td&gt;
&lt;td&gt;From $109/mo monthly or $96/mo annual&lt;/td&gt;
&lt;td&gt;Tier-based&lt;/td&gt;
&lt;td&gt;Branding removal, RSS-to-send, dynamic content, more publications, priority support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enterprise&lt;/td&gt;
&lt;td&gt;Custom&lt;/td&gt;
&lt;td&gt;100,000+ / custom&lt;/td&gt;
&lt;td&gt;SSO, dedicated infrastructure options, account management&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Email sending is unlimited across the normal newsletter plans, so costs are driven mainly by subscriber count and feature tier rather than campaign volume. beehiiv charges a 0% platform fee on paid subscriptions on the relevant paid plans, although normal payment-processing fees still apply.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kit
&lt;/h2&gt;

&lt;p&gt;Kit, formerly ConvertKit, sits between creator marketing software and newsletter publishing. Its free tier is unusually generous: up to 10,000 subscribers with unlimited email broadcasts, landing pages, forms, tagging, segmentation, and API access, designed to keep audience growth inexpensive. Paid plans add more sophisticated automation and analytics.&lt;/p&gt;

&lt;p&gt;Kit also provides public newsletter pages, which lets broadcasts function as web content rather than existing only inside inboxes. It makes the most sense when email subscribers are a core business asset and features such as sequences, segmentation, products, and creator-oriented automation matter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kit pricing
&lt;/h3&gt;

&lt;p&gt;The prices below use the 1,000-subscriber tier and annual billing for the paid plans.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Subscribers&lt;/th&gt;
&lt;th&gt;Notable features&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;Up to 10,000&lt;/td&gt;
&lt;td&gt;Unlimited broadcasts, forms and landing pages, tagging, segmentation, API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Creator&lt;/td&gt;
&lt;td&gt;$33/mo annual&lt;/td&gt;
&lt;td&gt;1,000 at quoted tier&lt;/td&gt;
&lt;td&gt;Visual automations, sequences, integrations, RSS campaigns, branding removal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Creator monthly equivalent&lt;/td&gt;
&lt;td&gt;About $39/mo&lt;/td&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;Same feature tier without annual discount&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;$66/mo annual&lt;/td&gt;
&lt;td&gt;1,000 at quoted tier&lt;/td&gt;
&lt;td&gt;Advanced analytics, subscriber scoring, referral system, unlimited users&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pro monthly equivalent&lt;/td&gt;
&lt;td&gt;About $79/mo&lt;/td&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;Same feature tier without annual discount&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Kit's paid prices increase with subscriber count.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ghost
&lt;/h2&gt;

&lt;p&gt;Ghost is a complete open-source publishing platform with newsletters built in: a website, CMS, memberships, newsletters, subscriptions, themes, APIs, and strong SEO controls. It can be self-hosted or purchased as the managed Ghost(Pro) service.&lt;/p&gt;

&lt;p&gt;Unlike most hosted newsletter platforms, self-hosted Ghost is open source, and hosting, database operation, and bulk email delivery then become separate infrastructure costs. Ghost is strong on SEO architecture: posts can specify external canonical URLs, and themes can access that canonical URL directly, which allows a Ghost archive to function as an index to content whose authoritative copy lives elsewhere.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;newsletter.example.net/
    Issue A -&amp;gt; example.com/newsletter/issue-a/
    Issue B -&amp;gt; example.com/newsletter/issue-b/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For organizations that need both a CMS and newsletters, Ghost is one of the most complete systems available. Deploying it only to send email, however, is unnecessarily heavy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ghost(Pro) pricing
&lt;/h3&gt;

&lt;p&gt;The prices below are annual-billing equivalents, and Ghost(Pro) pricing also changes with audience size.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Included members at entry tier&lt;/th&gt;
&lt;th&gt;Notable features&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Starter&lt;/td&gt;
&lt;td&gt;$18/mo&lt;/td&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;Website, custom domain, newsletter, unlimited sends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Publisher&lt;/td&gt;
&lt;td&gt;$29/mo&lt;/td&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;Custom themes, paid subscriptions, advanced analytics, integrations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Business&lt;/td&gt;
&lt;td&gt;$199/mo&lt;/td&gt;
&lt;td&gt;Higher limits&lt;/td&gt;
&lt;td&gt;15 staff users, priority support, larger usage limits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom&lt;/td&gt;
&lt;td&gt;Quote&lt;/td&gt;
&lt;td&gt;Custom / unlimited&lt;/td&gt;
&lt;td&gt;Dedicated IP, SLA, advanced configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Substack
&lt;/h2&gt;

&lt;p&gt;Substack removes most infrastructure decisions. A publication gets email delivery, public posts, archives, subscriber management, comments, recommendations, community features, and paid subscriptions without a monthly platform charge, so its pricing model is fundamentally different from subscriber-tiered services.&lt;/p&gt;

&lt;p&gt;Substack's public archive is central to the product: the platform expects its own web pages to be part of the publication rather than temporary copies of messages sent from another CMS. That is convenient for newsletter-first publishing, but it gives an external website less control over the public archive and the SEO architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  Substack pricing
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan / usage&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free publication&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;Unlimited free subscribers and publishing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free newsletters&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;No monthly platform fee&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Paid subscriptions&lt;/td&gt;
&lt;td&gt;10% of subscription revenue&lt;/td&gt;
&lt;td&gt;Charged by Substack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payment processing&lt;/td&gt;
&lt;td&gt;Additional&lt;/td&gt;
&lt;td&gt;Stripe processing charges also apply&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This makes Substack extremely inexpensive for a large free newsletter. The economics change once significant paid subscription revenue is involved: a newsletter producing $10,000 per month in subscription revenue would pay approximately $1,000 per month to Substack before payment-processing fees.&lt;/p&gt;

&lt;h2&gt;
  
  
  MailerLite
&lt;/h2&gt;

&lt;p&gt;MailerLite combines traditional email marketing with websites, landing pages, signup forms, and automation. Its free tier became substantially smaller in June 2026, dropping to 250 active subscribers and 2,500 email sends per month; in return, the free plan includes access to a broader set of design and publishing tools than many basic free newsletter products.&lt;/p&gt;

&lt;p&gt;MailerLite provides web versions of campaigns and a newsletter archive component that displays previous campaigns on a MailerLite-hosted page. That makes its public archive functionality stronger than EmailOctopus, although MailerLite is still primarily an email-marketing platform rather than an independent publishing ecosystem.&lt;/p&gt;

&lt;h3&gt;
  
  
  MailerLite pricing
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Subscribers / sending&lt;/th&gt;
&lt;th&gt;Notable features&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;250 subscribers, 2,500 emails/mo&lt;/td&gt;
&lt;td&gt;3 automations, 3 forms, 1 landing page, 1 small website&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Comfort&lt;/td&gt;
&lt;td&gt;From $12/mo&lt;/td&gt;
&lt;td&gt;Subscriber-tiered; monthly allowance is 10x tier size&lt;/td&gt;
&lt;td&gt;More templates, dynamic email, auto-resend, preference center&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Power&lt;/td&gt;
&lt;td&gt;Subscriber-tiered&lt;/td&gt;
&lt;td&gt;Unlimited sending under fair-use policy&lt;/td&gt;
&lt;td&gt;Higher feature limits and more advanced functionality&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enterprise&lt;/td&gt;
&lt;td&gt;Custom&lt;/td&gt;
&lt;td&gt;200,000+ subscribers&lt;/td&gt;
&lt;td&gt;Custom account arrangement&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Brevo
&lt;/h2&gt;

&lt;p&gt;Brevo is broader than a newsletter platform. Its product includes marketing email, transactional email, SMS, WhatsApp, automation, web push, sales tools, and customer-data functionality, and a newsletter is only one part of that platform. Its pricing is based primarily on sending volume rather than the number of stored contacts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Brevo pricing
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Sending / contacts&lt;/th&gt;
&lt;th&gt;Notable features&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;300 emails/day, up to 100,000 stored contacts&lt;/td&gt;
&lt;td&gt;Campaigns, transactional email, basic automation and reporting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Starter&lt;/td&gt;
&lt;td&gt;From $9/mo&lt;/td&gt;
&lt;td&gt;From 5,000 emails/mo&lt;/td&gt;
&lt;td&gt;No daily sending limit; multiple volume tiers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Standard&lt;/td&gt;
&lt;td&gt;From $18/mo&lt;/td&gt;
&lt;td&gt;From 5,000 emails/mo&lt;/td&gt;
&lt;td&gt;Unlimited-contact automation, A/B testing, advanced reports&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Professional&lt;/td&gt;
&lt;td&gt;From $499/mo&lt;/td&gt;
&lt;td&gt;From 150,000 emails/mo&lt;/td&gt;
&lt;td&gt;Multichannel features, 10 users, advanced analytics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enterprise&lt;/td&gt;
&lt;td&gt;Custom&lt;/td&gt;
&lt;td&gt;Custom&lt;/td&gt;
&lt;td&gt;Enterprise controls and support&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The free plan's 300-email daily limit is unusual. For a list with 250 subscribers it is perfectly usable; for a newsletter with 1,500 subscribers it becomes inconvenient because one full-list campaign cannot be delivered under the free daily allowance. Brevo becomes considerably more attractive when the same organization also needs application-generated email or multichannel marketing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sender
&lt;/h2&gt;

&lt;p&gt;Sender competes more directly with EmailOctopus and MailerLite. It offers newsletters, forms, landing pages, automation, segmentation, and transactional email, with one of the more generous free allowances in this group.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sender pricing
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Subscribers&lt;/th&gt;
&lt;th&gt;Email allowance&lt;/th&gt;
&lt;th&gt;Notable features&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free Forever&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;2,500&lt;/td&gt;
&lt;td&gt;15,000/mo&lt;/td&gt;
&lt;td&gt;Automation, forms, landing pages, transactional email; Sender branding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Standard annual&lt;/td&gt;
&lt;td&gt;From $7/mo&lt;/td&gt;
&lt;td&gt;From 1,000&lt;/td&gt;
&lt;td&gt;Roughly 12x subscriber tier/mo&lt;/td&gt;
&lt;td&gt;Branding removal, SMS, more templates and experiments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Standard monthly&lt;/td&gt;
&lt;td&gt;From about $9/mo&lt;/td&gt;
&lt;td&gt;From 1,000&lt;/td&gt;
&lt;td&gt;Tier-based&lt;/td&gt;
&lt;td&gt;Same plan without annual discount&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Professional&lt;/td&gt;
&lt;td&gt;From $14/mo annual&lt;/td&gt;
&lt;td&gt;From 1,000&lt;/td&gt;
&lt;td&gt;Roughly 24x subscriber tier/mo&lt;/td&gt;
&lt;td&gt;Advanced automation and reporting, priority support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enterprise&lt;/td&gt;
&lt;td&gt;Custom&lt;/td&gt;
&lt;td&gt;Custom&lt;/td&gt;
&lt;td&gt;Custom&lt;/td&gt;
&lt;td&gt;Enterprise sending infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At the free tier, 2,500 subscribers and 15,000 monthly sends allow approximately six full-list newsletters per month. Sender therefore compares well with EmailOctopus on raw free capacity, with the tradeoff that it has fewer integrations and less of a publication-oriented ecosystem than platforms such as Kit or beehiiv.&lt;/p&gt;

&lt;h2&gt;
  
  
  Public archives: three ownership models
&lt;/h2&gt;

&lt;p&gt;Archive functionality decides what happens to a multi-year back catalogue when the platform changes. Three models cover most cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Publication archive
&lt;/h3&gt;

&lt;p&gt;Platforms such as Substack, beehiiv, and Ghost treat each newsletter issue as part of a public publication on the provider's domain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider.example/publication/issue-1
provider.example/publication/issue-2
provider.example/publication/issue-3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The provider effectively becomes both newsletter infrastructure and CMS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hosted campaign copies
&lt;/h3&gt;

&lt;p&gt;Email-marketing services usually create a browser-readable version of an email after it has been sent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;email-provider.example/campaign/abc123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These URLs are useful for "view in browser" links and sharing. They should not necessarily be treated as the permanent content archive.&lt;/p&gt;

&lt;h3&gt;
  
  
  Website-owned archive
&lt;/h3&gt;

&lt;p&gt;A publisher with an existing website can keep the permanent copies itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;example.com/newsletter/issue-1/
example.com/newsletter/issue-2/
example.com/newsletter/issue-3/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The email provider then becomes replaceable infrastructure: changing newsletter vendors does not require changing historical URLs. For established websites this is the cleaner model, and it is the newsletter application of the &lt;a href="https://www.glukhov.org/post/2025/02/posse-publish-on-your-own-site-syndicate-elsewhere/" rel="noopener noreferrer"&gt;POSSE principle&lt;/a&gt;, which favors the copy you control over the copy a platform controls. For a static site, the archive is regular site content, as in the &lt;a href="https://www.glukhov.org/web-infrastructure/hugo/deploy-hugo-s3/" rel="noopener noreferrer"&gt;Hugo deployment to AWS S3&lt;/a&gt; guide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Canonical URLs and newsletter SEO
&lt;/h2&gt;

&lt;p&gt;If the complete issue appears both on an independent website and on a provider-hosted page, search engines can see two very similar documents. A canonical URL provides an explicit preferred version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"canonical"&lt;/span&gt;
      &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://example.com/newsletter/issue-42/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Buttondown and Ghost are particularly strong for this architecture because both provide mechanisms for associating content with an external canonical URL. Other newsletter services tend to assume that either their own hosted page is the publication or that their web version is simply an email mirror.&lt;/p&gt;

&lt;p&gt;This does not mean that every duplicate issue creates an SEO problem: search engines routinely consolidate duplicate or near-duplicate pages, and explicit canonical control simply makes the intended architecture clearer. Once the archive is in place, the &lt;a href="https://www.glukhov.org/web-infrastructure/seo/self-hosted-seo-tools-and-platforms/" rel="noopener noreferrer"&gt;self-hosted SEO tools comparison&lt;/a&gt; covers verification and monitoring, the &lt;a href="https://www.glukhov.org/web-infrastructure/wordpress/wordpress-seo-plugins-compared/" rel="noopener noreferrer"&gt;WordPress SEO plugins comparison&lt;/a&gt; covers the metadata layer for WordPress-based sites, and &lt;a href="https://www.glukhov.org/web-infrastructure/indexing/indexnow/" rel="noopener noreferrer"&gt;IndexNow&lt;/a&gt; can tell search engines that a new issue has been published.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom sending domains and sender authentication
&lt;/h2&gt;

&lt;p&gt;A serious newsletter should normally use an address under a domain controlled by the publisher:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;newsletter@example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rather than:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;examplepublication@gmail.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A custom domain can be authenticated with SPF, DKIM, and DMARC and can remain stable when the newsletter platform changes, so infrastructure can move from one provider to another while readers continue to see the same sender identity. This section covers the sending side; for mailbox hosting on the same domain, see the &lt;a href="https://www.glukhov.org/web-infrastructure/domain-services/email/" rel="noopener noreferrer"&gt;hosted email comparison&lt;/a&gt; instead.&lt;/p&gt;

&lt;p&gt;A practical setup sequence for a new sending domain:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Choose the sending domain, usually the main domain or a subdomain, and note it in the newsletter platform.&lt;/li&gt;
&lt;li&gt;Create the sending address in the platform and record the DKIM selector it publishes.&lt;/li&gt;
&lt;li&gt;Add the DNS records the platform provides: an SPF &lt;code&gt;TXT&lt;/code&gt; record listing the sending service, the DKIM &lt;code&gt;TXT&lt;/code&gt; record under the selector, and a DMARC &lt;code&gt;TXT&lt;/code&gt; record at &lt;code&gt;_dmarc&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Verify that each record resolves before sending:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="c"&gt;# SPF: which servers may send for the domain&lt;/span&gt;
   dig TXT example.com +short

   &lt;span class="c"&gt;# DKIM: the key published under the platform's selector&lt;/span&gt;
   dig TXT selector1._domainkey.example.com +short

   &lt;span class="c"&gt;# DMARC: the policy record for the domain&lt;/span&gt;
   dig TXT _dmarc.example.com +short
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a record is missing or mistyped, the platform's dashboard usually flags the domain as unverified, and sends either bounce or land in spam.&lt;/p&gt;

&lt;h2&gt;
  
  
  API access and automated publishing
&lt;/h2&gt;

&lt;p&gt;For an occasional newsletter, manually creating a campaign is not a problem. For a technical publication, API access becomes useful because newsletter generation can be integrated into the normal content pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A[Markdown source] --&amp;gt; B[Static site generator]
    B --&amp;gt; C[Website]
    A --&amp;gt; D[Newsletter build step]
    D --&amp;gt; E[Newsletter API]
    E --&amp;gt; F[Subscribers]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The same source material can generate the permanent page, the newsletter excerpts, the campaign metadata, and the links without repeatedly copying content between systems. The send step is a plain HTTP call; endpoint paths and payload fields differ per platform, but the pattern is the same:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://api.example.com/v1/campaigns"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$NEWSLETTER_API_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; @issue.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is one reason API-oriented platforms such as Buttondown, EmailOctopus, Kit, and Ghost remain attractive even when their visual editors are not the main selling point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing the architecture, not the editor
&lt;/h2&gt;

&lt;p&gt;The platform choice reduces to one decision: which system owns the content, and which system merely delivers it. A newsletter without an existing website saves a considerable amount of setup by letting a publishing-first platform own the posts, the archive, and the subscriptions. An established website usually gets a simpler long-term architecture by keeping the permanent archive on its own domain and treating the provider as delivery infrastructure that can be replaced without touching historical URLs.&lt;/p&gt;

&lt;p&gt;Whatever the split, three details are cheap to settle before the first send and expensive to unwind later: a custom sending domain with SPF, DKIM, and DMARC in place, an explicit decision about where each issue's canonical copy lives, and an export path that keeps the subscriber list in a standard format.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://buttondown.com/" rel="noopener noreferrer"&gt;Buttondown&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.emailoctopus.com/" rel="noopener noreferrer"&gt;EmailOctopus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.beehiiv.com/" rel="noopener noreferrer"&gt;beehiiv&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kit.com/" rel="noopener noreferrer"&gt;Kit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ghost.org/" rel="noopener noreferrer"&gt;Ghost&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://substack.com/" rel="noopener noreferrer"&gt;Substack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mailerlite.com/" rel="noopener noreferrer"&gt;MailerLite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.brevo.com/" rel="noopener noreferrer"&gt;Brevo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.sender.net/" rel="noopener noreferrer"&gt;Sender&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>seo</category>
      <category>webhosting</category>
    </item>
    <item>
      <title>Self-Hosted SEO Tools and Platforms: Open Source Guide</title>
      <dc:creator>Rost</dc:creator>
      <pubDate>Sun, 04 Oct 2026 11:03:32 +0000</pubDate>
      <link>https://dev.to/rosgluk/self-hosted-seo-tools-and-platforms-open-source-guide-15j6</link>
      <guid>https://dev.to/rosgluk/self-hosted-seo-tools-and-platforms-open-source-guide-15j6</guid>
      <description>&lt;p&gt;Self-hosted SEO now covers crawlers, Search Console dashboards, rank trackers, MCP servers, and local AI. This guide compares the main open-source platforms and which features work without paid SEO APIs.&lt;/p&gt;

&lt;p&gt;The term covers very different systems. Some projects work from your own website and first-party search data alone, while others self-host the interface but still require DataForSEO, a commercial SERP provider, or another paid API for their most valuable features.&lt;/p&gt;

&lt;p&gt;A dashboard wrapped around a paid data service is architecturally different from a crawler, a Search Console analyzer, or a local AI system that keeps working when every commercial API key is removed. That difference, not the location of the source code, decides which platforms fit a no-paid-API stack, and it is the axis of the comparison below. These systems run alongside deploy pipelines and indexing signals - the publishing infrastructure mapped in the &lt;a href="https://www.glukhov.org/web-infrastructure/" rel="noopener noreferrer"&gt;Web Infrastructure hub&lt;/a&gt; of this site.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is a Self-Hosted SEO Platform?
&lt;/h2&gt;

&lt;p&gt;A self-hosted SEO platform runs at least the application layer on infrastructure you control. Depending on the project, the application crawls websites, analyzes Search Console data, tracks rankings, inspects Core Web Vitals, generates reports, or exposes SEO functions to AI agents.&lt;/p&gt;

&lt;p&gt;The question that separates the projects is where the data comes from. A typical commercial SEO platform combines several expensive datasets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;keyword database
+
SERP collection
+
backlink crawler
+
site crawler
+
Search Console
+
analytics
+
content analysis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open-source projects reproduce some of these layers well. Site crawling, technical SEO, Search Console analysis, local page analysis, Core Web Vitals, and LLM-assisted recommendations are all practical to self-host.&lt;/p&gt;

&lt;p&gt;Other layers are much harder. A global backlink index requires continuously crawling a substantial part of the public web, and reliable worldwide keyword-volume and SERP datasets require either large-scale data collection or access to commercial sources. Open-source projects that promise Ahrefs-like data usually obtain at least part of it from another API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four Types of Self-Hosted SEO Tool
&lt;/h2&gt;

&lt;p&gt;The current ecosystem separates into four categories.&lt;/p&gt;

&lt;h3&gt;
  
  
  Technical crawlers
&lt;/h3&gt;

&lt;p&gt;These inspect the actual website and detect broken links, duplicate titles, missing metadata, canonical errors, heading problems, redirect chains, and accessibility issues.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/janreges/siteone-crawler" rel="noopener noreferrer"&gt;SiteOne Crawler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/PhialsBasement/LibreCrawl" rel="noopener noreferrer"&gt;LibreCrawl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/lokoe-mehdi/scouter" rel="noopener noreferrer"&gt;Scouter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They normally provide substantial value without any paid SEO API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Search performance and monitoring platforms
&lt;/h3&gt;

&lt;p&gt;These combine crawl data with first-party search information, particularly Google Search Console.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/crawlseo/crawlseo" rel="noopener noreferrer"&gt;CrawlSEO&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fenjo26/opengsc" rel="noopener noreferrer"&gt;OpenGSC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/iannuttall/seo" rel="noopener noreferrer"&gt;SEO Skill&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For an established website, this category is often more useful than a generic keyword database because it works with real impressions, clicks, CTR, rankings, and the pages Google already associates with your domain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rank and SERP tools
&lt;/h3&gt;

&lt;p&gt;These collect search engine results and track positions.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/towfiqi/serpbear" rel="noopener noreferrer"&gt;SerpBear&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fredyglen/OpenSERP" rel="noopener noreferrer"&gt;OpenSERP&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The difficulty here is operational rather than conceptual. Search engines rate-limit automated access, change markup, present CAPTCHAs, and personalize results. A self-hosted SERP system can work, but it requires more maintenance than calling a commercial SERP API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Full SEO platforms
&lt;/h3&gt;

&lt;p&gt;These attempt to bring keyword research, backlinks, competitor analysis, site audits, rank tracking, and AI workflows into one product. The most visible example is &lt;a href="https://github.com/every-app/open-seo" rel="noopener noreferrer"&gt;OpenSEO&lt;/a&gt;. These systems provide an attractive interface, and their external data often still comes from commercial APIs, so inspect the data path before deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-Hosted SEO Tools Compared
&lt;/h2&gt;

&lt;p&gt;GitHub star and fork counts are approximate snapshots from September 2026. They are adoption signals, not quality scores.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;System&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;GitHub stars&lt;/th&gt;
&lt;th&gt;Forks&lt;/th&gt;
&lt;th&gt;Web UI&lt;/th&gt;
&lt;th&gt;CLI&lt;/th&gt;
&lt;th&gt;MCP&lt;/th&gt;
&lt;th&gt;Local LLM&lt;/th&gt;
&lt;th&gt;Paid SEO API needed for core value&lt;/th&gt;
&lt;th&gt;License&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OpenSEO&lt;/td&gt;
&lt;td&gt;Full SEO platform&lt;/td&gt;
&lt;td&gt;20.5K&lt;/td&gt;
&lt;td&gt;2.6K&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Not primary&lt;/td&gt;
&lt;td&gt;Yes, DataForSEO&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SerpBear&lt;/td&gt;
&lt;td&gt;Rank tracker&lt;/td&gt;
&lt;td&gt;2.1K&lt;/td&gt;
&lt;td&gt;294&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No, if using own proxies&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LibreCrawl&lt;/td&gt;
&lt;td&gt;Technical crawler&lt;/td&gt;
&lt;td&gt;980&lt;/td&gt;
&lt;td&gt;206&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SiteOne Crawler&lt;/td&gt;
&lt;td&gt;Technical crawler / QA&lt;/td&gt;
&lt;td&gt;917&lt;/td&gt;
&lt;td&gt;84&lt;/td&gt;
&lt;td&gt;Reports / desktop&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No native&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CrawlSEO&lt;/td&gt;
&lt;td&gt;Monitoring platform&lt;/td&gt;
&lt;td&gt;601&lt;/td&gt;
&lt;td&gt;92&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Agent-side&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SEO Skill&lt;/td&gt;
&lt;td&gt;SEO CLI / agent backend&lt;/td&gt;
&lt;td&gt;532&lt;/td&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Agent-side&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scouter&lt;/td&gt;
&lt;td&gt;AI-native crawler&lt;/td&gt;
&lt;td&gt;71&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Some&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenGSC&lt;/td&gt;
&lt;td&gt;GSC platform&lt;/td&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No for core&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenSERP&lt;/td&gt;
&lt;td&gt;SERP API / CLI&lt;/td&gt;
&lt;td&gt;Varies&lt;/td&gt;
&lt;td&gt;Varies&lt;/td&gt;
&lt;td&gt;API docs&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Add-on&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The most important column is whether useful functionality survives when commercial SEO API keys are removed. OpenSEO's 20,000 stars reflect broad appeal and an all-in-one proposition, not self-containment, and a young project such as Scouter can offer a more interesting local-LLM architecture with far fewer stars. Before choosing, it is better to ask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Does the project solve my problem?
Does its core still work without a paid API?
Can I export my data?
Can I automate it?
Is the project maintained?
Can I replace individual components later?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  OpenSEO
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/every-app/open-seo" rel="noopener noreferrer"&gt;OpenSEO&lt;/a&gt; is the largest project in this comparison, with roughly 20.5 thousand GitHub stars and 2.6 thousand forks at the time of writing. The project describes itself as an open-source alternative to Semrush and Ahrefs and provides keyword research, rank tracking, competitor intelligence, backlinks, site audits, AI visibility, MCP, and agent skills.&lt;/p&gt;

&lt;p&gt;Its strongest feature is product integration: a modern application with project context, SEO workflows, and an MCP interface for clients such as Claude Code, Codex, OpenClaw, and Hermes, instead of assembling several command-line utilities.&lt;/p&gt;

&lt;p&gt;The data dependency is the other half of the picture. The project documentation explicitly requires a &lt;a href="https://dataforseo.com/" rel="noopener noreferrer"&gt;DataForSEO&lt;/a&gt; API key for SEO data, and DataForSEO is a separate pay-as-you-go commercial service. Self-hosting OpenSEO does not self-host the underlying dataset. The architecture is:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A[Website] --&amp;gt; B[OpenSEO]
    C[AI Agent] --&amp;gt;|MCP| B
    B --&amp;gt; D[DataForSEO]
    B --&amp;gt; E[Google Search Console]
    B --&amp;gt; F[OpenSEO database]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;If you already use DataForSEO, OpenSEO is an open-source control plane around it, and self-hosting still gives you control over projects, credentials, application state, and agent integration. It is not a replacement for paid external SEO data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best use
&lt;/h3&gt;

&lt;p&gt;Choose OpenSEO when you want a modern Semrush-style interface and are comfortable paying DataForSEO directly instead of paying for a traditional SEO SaaS subscription. Do not choose it if the requirement is that the system retains most of its value with no paid SEO API at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  SiteOne Crawler
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/janreges/siteone-crawler" rel="noopener noreferrer"&gt;SiteOne Crawler&lt;/a&gt; is one of the most self-contained tools in the list. It has around 917 GitHub stars and 84 forks and is designed as a cross-platform website crawler for SEO, security, accessibility, performance, and quality assurance.&lt;/p&gt;

&lt;p&gt;The SEO checks cover the technical layer: metadata, links, redirects, page structure, status codes, sitemaps, and related issues. SiteOne also inspects security headers, TLS, accessibility, caching, and performance, which makes it useful for developer-managed websites where SEO is part of a wider deployment-quality process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Browser rendering for modern sites
&lt;/h3&gt;

&lt;p&gt;SiteOne can optionally render pages through Chromium using the Chrome DevTools Protocol. That matters for React, Vue, Angular, and other sites where important links or content only appear after JavaScript execution. For static systems such as Hugo, browser rendering is usually unnecessary, but it is useful when testing embedded applications, client-side widgets, or pages with significant JavaScript behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  Local LLM support
&lt;/h3&gt;

&lt;p&gt;SiteOne's optional AI layer supports OpenAI, Anthropic, Gemini, and arbitrary OpenAI-compatible endpoints. The OpenAI-compatible path can point at Ollama, vLLM, LocalAI, self-hosted gateways, or other compatible servers, so a local model can inspect selected pages without sending content to a cloud provider.&lt;/p&gt;

&lt;p&gt;A fully local architecture:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A[Website] --&amp;gt; B[SiteOne]
    B --&amp;gt; C[Deterministic crawl results]
    C --&amp;gt; D[Local OpenAI-compatible API]
    D --&amp;gt; E[Ollama / llama.cpp / vLLM]
    E --&amp;gt; F[SEO suggestions]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;SiteOne determines facts such as missing descriptions or broken links, while the model proposes better wording or summarizes the findings.&lt;/p&gt;

&lt;h3&gt;
  
  
  CI/CD quality gate
&lt;/h3&gt;

&lt;p&gt;A practical workflow:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A[Git push] --&amp;gt; B[Build website]
    B --&amp;gt; C[Local preview]
    C --&amp;gt; D[SiteOne crawl]
    D --&amp;gt; E{Critical issues?}
    E --&amp;gt;|Yes| F[Fail pipeline]
    E --&amp;gt;|No| G[Deploy]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The crawl runs against a local preview before deploy, and the pipeline fails on critical issues. That gives engineering teams an automated check instead of a dashboard that is reviewed occasionally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best use
&lt;/h3&gt;

&lt;p&gt;Choose SiteOne for technical websites, static-site generators, CI/CD pipelines, or any environment where SEO checks should behave like automated software-quality checks. It is a strong option when the priority is genuine local value rather than external SEO datasets.&lt;/p&gt;

&lt;h2&gt;
  
  
  LibreCrawl
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/PhialsBasement/LibreCrawl" rel="noopener noreferrer"&gt;LibreCrawl&lt;/a&gt; is a web-based multi-user SEO crawler with around 980 GitHub stars and 206 forks. The project positions it as an open-source alternative to desktop crawlers such as Screaming Frog.&lt;/p&gt;

&lt;p&gt;LibreCrawl extracts page titles, descriptions, headings, links, response information, and other technical SEO data. It supports JavaScript rendering, configurable crawl depth, URL filtering, proxies, robots.txt behavior, and exports to CSV, XLSX, JSON, and XML. Unlike a desktop-only crawler, it runs as a web application and supports concurrent users with separate sessions.&lt;/p&gt;

&lt;h3&gt;
  
  
  External dependencies
&lt;/h3&gt;

&lt;p&gt;The core crawler works locally. PageSpeed Insights integration can use Google's API and benefits from an API key for higher limits, but the crawler itself does not require a commercial SEO data service.&lt;/p&gt;

&lt;p&gt;Because LibreCrawl operates against the rendered website rather than the CMS, it works with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hugo
WordPress
Ghost
Drupal
Next.js
Astro
React
custom websites
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Limitations
&lt;/h3&gt;

&lt;p&gt;LibreCrawl does not offer the agent-oriented architecture of Scouter or SEO Skill: there is no central MCP workflow, and local LLM integration is not its main focus. Its strength is straightforward crawling and data export.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best use
&lt;/h3&gt;

&lt;p&gt;Choose LibreCrawl when you want a browser-based, open-source crawler with a familiar human workflow and good export support.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scouter
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/lokoe-mehdi/scouter" rel="noopener noreferrer"&gt;Scouter&lt;/a&gt; is a younger project, but its architecture is the most ambitious in this list for AI-native SEO workflows. It has roughly 71 GitHub stars and 7 forks, so its deployment history is tiny compared with OpenSEO or SerpBear. The documented feature set includes a self-hosted web UI, multi-user support, JavaScript rendering, internal PageRank, custom XPath and regex extractors, SQL access to crawl data, AI page categorization, AI bulk generation, and a native MCP server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bring your own LLM
&lt;/h3&gt;

&lt;p&gt;Scouter supports hosted and local models; the documented local options are Ollama and vLLM. The resulting architecture:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    A[Website] --&amp;gt; B[Scouter crawler]
    B --&amp;gt; C[Crawl database]
    C --&amp;gt; D[Web UI]
    C --&amp;gt; E[MCP]
    C --&amp;gt; F[AI assistant]
    F --&amp;gt; G[Local Ollama / vLLM]
    E --&amp;gt; H[AI agent]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;This is closer to an "SEO database for agents" than to a traditional crawler.&lt;/p&gt;

&lt;h3&gt;
  
  
  MCP integration
&lt;/h3&gt;

&lt;p&gt;Without MCP, an AI assistant usually needs reports pasted into its context or custom scripts written around exported files. With MCP, the agent asks the SEO system directly for structured crawl evidence, triggers operations, and retrieves relevant findings as needed. Workflows such as this become realistic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Find pages with weak internal linking.

For each page:
- show inlink count
- identify semantically related pages
- propose links
- do not modify content
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The crawler supplies the facts, the model provides the interpretation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Limitations
&lt;/h3&gt;

&lt;p&gt;Scouter is new. A 2026 project with around 60 commits and a small user base is promising infrastructure rather than a proven production standard, and its more complex stack means more operational components than a single-binary crawler such as SiteOne.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best use
&lt;/h3&gt;

&lt;p&gt;Choose Scouter when MCP, multi-user web access, SQL over crawl data, and local AI integration are important enough to justify running a younger platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  SEO Skill (iannuttall/seo)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/iannuttall/seo" rel="noopener noreferrer"&gt;SEO Skill&lt;/a&gt; takes a different approach: instead of a large browser dashboard, it provides a local &lt;code&gt;seo&lt;/code&gt; CLI, a TypeScript library, a packaged agent skill, and an MCP server. The project has around 532 GitHub stars and 42 forks.&lt;/p&gt;

&lt;p&gt;The workflow combines evidence from your own crawl, Google Search Console, and optional analytics into reports that humans or AI agents can inspect and act upon. Useful commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;seo report
seo quick-wins
seo second-page
seo technical-watch
seo refresh-priorities
seo report --json
seo mcp serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The project exposes more than 70 SEO audit tools through its CLI and MCP layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  First-party data first
&lt;/h3&gt;

&lt;p&gt;SEO Skill treats paid research providers as optional enrichment rather than mandatory infrastructure. It can use local crawl data, Google Search Console, and analytics data, and it can optionally connect to DataForSEO, Semrush, or Ahrefs. If none of the commercial providers are configured, the first-party workflow still works.&lt;/p&gt;

&lt;p&gt;For an established website, a query with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;8,000 impressions
average position 8.2
CTR 1.1%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;describes your actual opportunity, while a third-party estimate claiming that a related keyword receives 12,000 searches per month describes a market you cannot verify from your own data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Agent architecture
&lt;/h3&gt;



&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A[Website] --&amp;gt; B[SEO Skill crawl]
    C[Google Search Console] --&amp;gt; B
    D[Analytics] --&amp;gt; B
    B --&amp;gt; E[MCP server]
    E --&amp;gt; F[Hermes / Claude / Codex]
    F --&amp;gt; G[Local LLM]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The LLM receives structured evidence from the SEO tool instead of scraping dashboards or guessing metrics.&lt;/p&gt;

&lt;h3&gt;
  
  
  Limitations
&lt;/h3&gt;

&lt;p&gt;SEO Skill is not a polished multi-user SEO dashboard. If you want a visual application that marketing staff can browse all day, another system may fit better. External competitor, backlink, and keyword research remains limited unless optional third-party providers are added.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best use
&lt;/h3&gt;

&lt;p&gt;Choose SEO Skill when you want an agent-first, scriptable SEO backend built around first-party evidence rather than a large GUI. For developers, static-site publishers, and automated Git workflows, it is a strong fit.&lt;/p&gt;

&lt;h2&gt;
  
  
  CrawlSEO
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/crawlseo/crawlseo" rel="noopener noreferrer"&gt;CrawlSEO&lt;/a&gt; is a self-hosted SEO monitoring dashboard with around 601 GitHub stars and 92 forks. It combines Google Search Console, a site crawler, Core Web Vitals, SEO opportunities, and MCP access, which puts it between a crawler and a full SEO platform.&lt;/p&gt;

&lt;p&gt;The core data sources are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google Search Console&lt;/li&gt;
&lt;li&gt;local site crawling&lt;/li&gt;
&lt;li&gt;Core Web Vitals / PageSpeed data&lt;/li&gt;
&lt;li&gt;stored historical observations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CrawlSEO can provide ongoing monitoring without a paid SEO provider.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optional external research
&lt;/h3&gt;

&lt;p&gt;CrawlSEO can integrate with DataForSEO for keyword research and backlink information, but those features are optional, and Google Autocomplete provides a free fallback for keyword suggestions. The core stack runs without an external SEO dataset:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GSC
+
crawler
+
Core Web Vitals
+
MCP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  MCP tools
&lt;/h3&gt;

&lt;p&gt;CrawlSEO exposes ten MCP tools covering sites, site overview, keywords, pages, traffic, crawling, crawl issues, Core Web Vitals, and opportunities. An agent can ask questions such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Which pages lost impressions this month and also have technical crawl issues?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That correlation between search performance and technical state is more actionable than a generic SEO score.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best use
&lt;/h3&gt;

&lt;p&gt;Choose CrawlSEO if you want a persistent self-hosted dashboard built around Search Console and ongoing monitoring rather than one-off audits. It is particularly suitable for established sites that already receive meaningful Google search impressions.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenGSC
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/fenjo26/opengsc" rel="noopener noreferrer"&gt;OpenGSC&lt;/a&gt; is another Search Console-centered platform. It is a young project, with roughly 27 stars and 19 forks, but it has accumulated a substantial feature set. The core proposition: Search Console contains valuable first-party SEO data, but Google's own interface is limited for longitudinal analysis, cross-site monitoring, content-decay detection, and agent workflows.&lt;/p&gt;

&lt;p&gt;OpenGSC adds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multi-site GSC dashboards&lt;/li&gt;
&lt;li&gt;rank tracking from first-party data&lt;/li&gt;
&lt;li&gt;striking-distance queries&lt;/li&gt;
&lt;li&gt;content-decay detection&lt;/li&gt;
&lt;li&gt;cannibalization analysis&lt;/li&gt;
&lt;li&gt;site auditing&lt;/li&gt;
&lt;li&gt;indexing tools&lt;/li&gt;
&lt;li&gt;alerts and digests&lt;/li&gt;
&lt;li&gt;MCP&lt;/li&gt;
&lt;li&gt;optional AI SEO tools&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Built-in site audit
&lt;/h3&gt;

&lt;p&gt;The crawler operates with zero external SEO APIs and checks up to hundreds of pages for broken internal links, title issues, missing descriptions, H1 problems, noindex pages, canonical mismatches, thin content, missing image alt text, and slow responses. The platform retains useful local functionality even when its optional research integrations are disabled.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI and external providers
&lt;/h3&gt;

&lt;p&gt;OpenGSC supports multiple AI providers and custom OpenAI-compatible endpoints, which makes local inference possible depending on the configured server. Some extended research features can use DataForSEO or other external providers, but they are not required for the Search Console dashboard and local audit layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Caution around optional indexing features
&lt;/h3&gt;

&lt;p&gt;OpenGSC includes optional indexing-oriented infrastructure that goes beyond normal Search Console analysis. Evaluate those features independently; they are not required for ordinary SEO monitoring. For conservative deployments, the sensible configuration is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GSC analytics
+
site audit
+
MCP
+
optional local AI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rather than enabling every available module.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best use
&lt;/h3&gt;

&lt;p&gt;Choose OpenGSC if Search Console is the center of your SEO workflow and you want a self-hosted interface around your own search-performance data.&lt;/p&gt;

&lt;h2&gt;
  
  
  SerpBear
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/towfiqi/serpbear" rel="noopener noreferrer"&gt;SerpBear&lt;/a&gt; is one of the most mature open-source rank trackers, with around 2.1 thousand GitHub stars and 294 forks. Its purpose is narrower than OpenSEO or CrawlSEO: track where a domain appears in Google for a configured set of keywords.&lt;/p&gt;

&lt;p&gt;It provides unlimited domains, unlimited tracked keywords, ranking history, email notifications, a SERP API, Google Search Console integration, keyword research through Google Ads integration, and PWA/mobile access.&lt;/p&gt;

&lt;h3&gt;
  
  
  The SERP collection problem
&lt;/h3&gt;

&lt;p&gt;The application, database, scheduler, and UI are self-hosted, but Google results still have to be collected somehow. SerpBear can use your own proxies, scraping services, or commercial SERP APIs. If you operate your own proxies, no commercial SEO API is inherently required, but the operational burden moves to you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;commercial SERP API:
  money cost
  low operational burden

self-hosted scraping:
  low API cost
  higher operational burden
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Best use
&lt;/h3&gt;

&lt;p&gt;Choose SerpBear when rank tracking is the main requirement and you are comfortable managing the SERP acquisition layer yourself. It is a specialized tool that works well alongside a crawler or Search Console platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenSERP
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/fredyglen/OpenSERP" rel="noopener noreferrer"&gt;OpenSERP&lt;/a&gt; is not a complete SEO dashboard. It is infrastructure: a self-hosted SERP API and CLI.&lt;/p&gt;

&lt;p&gt;It supports search against Google, Bing, DuckDuckGo, Yandex, Baidu, and Ecosia, and returns normalized results through JSON and other formats. OpenSERP can also expose SERP features such as AI summaries, answer boxes, People Also Ask, and related searches, and it can optionally extract content from result pages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it matters
&lt;/h3&gt;

&lt;p&gt;Commercial SEO tools often hide SERP acquisition behind a polished application. OpenSERP provides the lower-level component, which makes it useful for building rank tracking, competitor discovery, SERP-intent analysis, query clustering, agent search tools, and local research pipelines.&lt;/p&gt;

&lt;p&gt;Example architecture:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A[Keyword list] --&amp;gt; B[OpenSERP]
    B --&amp;gt; C[Search engines]
    B --&amp;gt; D[Local result store]
    D --&amp;gt; E[SEO analysis]
    E --&amp;gt; F[Local LLM]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The project also provides SDKs and an MCP integration, making it suitable as infrastructure behind other systems. For the wider self-hosted search landscape, see &lt;a href="https://www.glukhov.org/post/2025/10/alternative-search-engines/" rel="noopener noreferrer"&gt;Beyond Google: Alternative Search Engines Guide&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Limitations
&lt;/h3&gt;

&lt;p&gt;Running the API yourself does not make search engines easier to scrape. CAPTCHAs, blocking, rate limits, regional variation, and result-layout changes remain real problems, and proxies may eventually become necessary at meaningful scale. OpenSERP is best treated as a controlled local SERP source, not a free substitute for a commercial global search dataset.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best use
&lt;/h3&gt;

&lt;p&gt;Choose OpenSERP when you want to own the SERP collection pipeline or need search-engine results as an input to custom SEO automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which Tools Work Without Paid SEO APIs?
&lt;/h2&gt;

&lt;p&gt;This comparison is more useful than "open source versus proprietary."&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;System&lt;/th&gt;
&lt;th&gt;Technical audit&lt;/th&gt;
&lt;th&gt;GSC analysis&lt;/th&gt;
&lt;th&gt;SERP/rank tracking&lt;/th&gt;
&lt;th&gt;Local AI&lt;/th&gt;
&lt;th&gt;Useful with zero paid SEO APIs?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OpenSEO&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Agent-oriented&lt;/td&gt;
&lt;td&gt;Partly, but core research depends on DataForSEO&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SiteOne&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LibreCrawl&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scouter&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;No core GSC focus&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SEO Skill&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;First-party/optional providers&lt;/td&gt;
&lt;td&gt;Via agent&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CrawlSEO&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;GSC based&lt;/td&gt;
&lt;td&gt;Via agent&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenGSC&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;GSC based&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SerpBear&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes, with own scraping/proxies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenSERP&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;SERP infrastructure&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes, operationally limited&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The functions easiest to self-host are the ones based on your own site and your own search data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hugo, WordPress, Ghost, or Something Else?
&lt;/h2&gt;

&lt;p&gt;Most of these systems do not care which CMS or static-site generator produced the website. A crawler sees HTTP and HTML, so Hugo, WordPress, Ghost, Drupal, Astro, Next.js, Jekyll, and custom applications can all be analyzed by the same crawler.&lt;/p&gt;

&lt;p&gt;The difference appears when you want to modify the site. A WordPress SEO plugin can directly update post metadata because it runs inside WordPress. SiteOne or Scouter cannot automatically know how your Hugo front matter is structured unless another integration layer tells them. The separation:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A[CMS / static-site generator] --&amp;gt; B[Published website]
    B --&amp;gt; C[Self-hosted SEO system]
    C --&amp;gt; D[Findings]
    D --&amp;gt; E[Human or AI agent]
    E --&amp;gt; F[Source changes]
    F --&amp;gt; A&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;For modern development workflows, this separation is often an advantage rather than a problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-Hosted SEO for WordPress
&lt;/h2&gt;

&lt;p&gt;For WordPress, a self-hosted SEO platform normally sits beside a conventional SEO plugin:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    A[WordPress]
    A --&amp;gt; B[The SEO Framework / SEOPress]
    B --&amp;gt; C[Metadata / schema / sitemap]
    A --&amp;gt; D[Published site]
    D --&amp;gt; E[SiteOne / CrawlSEO / Scouter]
    E --&amp;gt; F[SEO findings]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The plugin controls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;canonical URLs
robots metadata
schema
XML sitemaps
titles and descriptions
redirects
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The external platform observes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;technical health
crawl structure
search performance
Core Web Vitals
rank changes
content opportunities
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are complementary responsibilities; plugin-by-plugin comparisons of Yoast, Rank Math, SEOPress, The SEO Framework, and Slim SEO belong to the WordPress side of the stack, not to the monitoring layer described here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-Hosted SEO for Hugo
&lt;/h2&gt;

&lt;p&gt;Static-site generators such as Hugo benefit particularly from external SEO tooling because they usually lack a large CMS plugin ecosystem. A good Hugo workflow treats SEO like software quality:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A[Markdown] --&amp;gt; B[Hugo build]
    B --&amp;gt; C[Preview site]
    C --&amp;gt; D[SiteOne]
    D --&amp;gt; E{Technical checks pass?}
    E --&amp;gt;|No| F[Fix source]
    E --&amp;gt;|Yes| G[Deploy]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;If you &lt;a href="https://www.glukhov.org/web-infrastructure/hugo/deploy-hugo-s3/" rel="noopener noreferrer"&gt;deploy Hugo to S3&lt;/a&gt;, the crawl step slots into the same pipeline that builds and publishes the site, and &lt;a href="https://www.glukhov.org/web-infrastructure/indexing/indexnow/" rel="noopener noreferrer"&gt;IndexNow notifications&lt;/a&gt; complement the crawler by telling engines when new URLs exist.&lt;/p&gt;

&lt;p&gt;Production monitoring then uses Search Console:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A[Published Hugo site] --&amp;gt; B[Google]
    B --&amp;gt; C[Search Console]
    A --&amp;gt; D[Local crawler]
    C --&amp;gt; E[SEO Skill / CrawlSEO]
    D --&amp;gt; E
    E --&amp;gt; F[Prioritized opportunities]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The workflow evaluates the website that users and search engines actually receive, not the editor's view of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local LLMs in Self-Hosted SEO
&lt;/h2&gt;

&lt;p&gt;Local LLMs add another layer, but they should not become the source of truth. A model is good at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rewriting titles&lt;/li&gt;
&lt;li&gt;proposing descriptions&lt;/li&gt;
&lt;li&gt;clustering queries&lt;/li&gt;
&lt;li&gt;summarizing crawl findings&lt;/li&gt;
&lt;li&gt;identifying semantic gaps&lt;/li&gt;
&lt;li&gt;suggesting internal links&lt;/li&gt;
&lt;li&gt;comparing content structure&lt;/li&gt;
&lt;li&gt;drafting update plans&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is poor as the authoritative source for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTP status codes&lt;/li&gt;
&lt;li&gt;rankings&lt;/li&gt;
&lt;li&gt;clicks&lt;/li&gt;
&lt;li&gt;impressions&lt;/li&gt;
&lt;li&gt;indexing state&lt;/li&gt;
&lt;li&gt;canonical correctness&lt;/li&gt;
&lt;li&gt;backlink counts&lt;/li&gt;
&lt;li&gt;Core Web Vitals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architecture:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A[Crawler / GSC / SERP source] --&amp;gt; B[Structured evidence]
    B --&amp;gt; C[Local LLM]
    C --&amp;gt; D[Recommendation]
    D --&amp;gt; E[Review]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;rather than asking the model "Audit my site and tell me how it ranks."&lt;/p&gt;

&lt;p&gt;SiteOne and Scouter provide native routes to local models. SEO Skill and CrawlSEO are useful when the LLM lives in an external agent such as Hermes, Claude Code, or another MCP client. Running the model on your own infrastructure keeps your content there, the same argument as the broader &lt;a href="https://www.glukhov.org/llm-hosting/self-hosting/llm-selfhosting-and-ai-sovereignty/" rel="noopener noreferrer"&gt;LLM self-hosting and AI sovereignty&lt;/a&gt; case applied to SEO. For the model server itself, the &lt;a href="https://www.glukhov.org/llm-hosting/ollama/ollama-cheatsheet/" rel="noopener noreferrer"&gt;Ollama CLI cheatsheet&lt;/a&gt; covers serving and model management.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP in Self-Hosted SEO
&lt;/h2&gt;

&lt;p&gt;Model Context Protocol changes how SEO software interacts with AI systems.&lt;/p&gt;

&lt;p&gt;Without MCP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SEO tool
-&amp;gt; export CSV
-&amp;gt; paste into AI
-&amp;gt; ask question
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With MCP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI agent
-&amp;gt; request crawl evidence
-&amp;gt; request GSC evidence
-&amp;gt; request affected URLs
-&amp;gt; analyze
-&amp;gt; propose change
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent retrieves only the evidence it needs. Projects in this comparison that already support MCP: OpenSEO, SEO Skill, CrawlSEO, Scouter, OpenGSC, and OpenSERP through its MCP package. If you have built the server side of this integration, the &lt;a href="https://www.glukhov.org/ai-systems/mcp/mcp-server-in-go/" rel="noopener noreferrer"&gt;MCP server implementation notes in Go&lt;/a&gt; show what an MCP server looks like from the inside.&lt;/p&gt;

&lt;p&gt;A properly designed SEO agent could:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Find pages losing impressions.
2. Check whether the pages have crawl issues.
3. Read their dominant Search Console queries.
4. Find weak internal linking.
5. Propose a change.
6. Modify the source on a Git branch.
7. Run technical validation.
8. Open a pull request.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SEO system remains the evidence provider, and the agent becomes the workflow engine.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Can Actually Be Replaced Locally?
&lt;/h2&gt;

&lt;p&gt;A realistic self-hosted stack can replace a substantial share of commercial SEO functionality.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;Practical self-hosted option&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Technical crawling&lt;/td&gt;
&lt;td&gt;SiteOne, LibreCrawl, Scouter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Metadata checks&lt;/td&gt;
&lt;td&gt;SiteOne, LibreCrawl, Scouter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internal link analysis&lt;/td&gt;
&lt;td&gt;Scouter, SiteOne, crawl exports&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search performance&lt;/td&gt;
&lt;td&gt;GSC + SEO Skill / CrawlSEO / OpenGSC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content decay&lt;/td&gt;
&lt;td&gt;GSC-based platforms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Core Web Vitals&lt;/td&gt;
&lt;td&gt;CrawlSEO, Lighthouse, PageSpeed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rank tracking&lt;/td&gt;
&lt;td&gt;SerpBear, OpenSERP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SERP inspection&lt;/td&gt;
&lt;td&gt;OpenSERP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI analysis&lt;/td&gt;
&lt;td&gt;Local Ollama / vLLM / llama.cpp&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent automation&lt;/td&gt;
&lt;td&gt;MCP-enabled SEO systems&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI quality gates&lt;/td&gt;
&lt;td&gt;SiteOne&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Historical monitoring&lt;/td&gt;
&lt;td&gt;CrawlSEO / OpenGSC&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The two major gaps remain global keyword data and backlinks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Difficult to Self-Host?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Global keyword volume
&lt;/h3&gt;

&lt;p&gt;Search volume looks simple when displayed as one number:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"local llm" -&amp;gt; 12,100 searches/month
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but obtaining that number reliably requires access to a very large dataset. Google Search Console only shows queries for which your own site already appeared; it cannot describe the demand landscape for a topic you have never covered. Google Ads provides some keyword information, but building a Semrush-like global keyword database locally is not realistic for most individuals.&lt;/p&gt;

&lt;h3&gt;
  
  
  Global backlink index
&lt;/h3&gt;

&lt;p&gt;Backlink analysis is more infrastructure-heavy. Ahrefs, Semrush, Majestic, and similar companies continuously crawl huge portions of the web, normalize URLs, identify links, deduplicate data, and maintain historical indexes. An individual can crawl selected competitor sites or monitor links discovered through Search Console and analytics, but that is not equivalent to a web-scale backlink database. Open-source SEO products integrate DataForSEO, Ahrefs, or another provider for these two categories because the underlying data is expensive to collect.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical Fully Self-Hosted Stack
&lt;/h2&gt;

&lt;p&gt;If the goal is useful SEO without a paid SEO API, combine specialized components rather than looking for one giant application:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    SITE[Website]
    GSC[Google Search Console]
    CRAWL[SiteOne]
    SEO[SEO Skill]
    SERP[OpenSERP]
    AGENT[AI Agent]
    LLM[Local LLM]
    GIT[Git / CMS]

    SITE --&amp;gt; CRAWL
    SITE --&amp;gt; GSC
    GSC --&amp;gt; SEO
    CRAWL --&amp;gt; SEO
    SERP --&amp;gt; SEO
    SEO --&amp;gt; AGENT
    AGENT --&amp;gt; LLM
    AGENT --&amp;gt; GIT&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The responsibilities:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SiteOne:
  technical crawl
  CI regression checks

SEO Skill:
  Search Console
  first-party opportunity analysis
  MCP

OpenSERP:
  optional SERP observations

Local LLM:
  interpretation and language tasks

Agent:
  workflow orchestration

Git/CMS:
  controlled publication changes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This stack cannot reproduce every Ahrefs feature, but it covers most of the work involved in improving an existing website.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing a Tool for Your Requirement
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Primary requirement&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Deterministic technical checks, CI/CD use, optional local AI&lt;/td&gt;
&lt;td&gt;SiteOne&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Human-friendly web crawler with exports and JavaScript rendering&lt;/td&gt;
&lt;td&gt;LibreCrawl&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI-native crawler: MCP, SQL access, multi-user UI, local Ollama/vLLM&lt;/td&gt;
&lt;td&gt;Scouter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CLI/MCP backend combining crawl and Search Console evidence&lt;/td&gt;
&lt;td&gt;SEO Skill&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Persistent dashboard: GSC, crawling, Core Web Vitals, agent access&lt;/td&gt;
&lt;td&gt;CrawlSEO&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search Console-centered dashboard: decay, striking-distance queries, agent access&lt;/td&gt;
&lt;td&gt;OpenGSC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keyword rank tracking, self-managed proxies or SERP provider&lt;/td&gt;
&lt;td&gt;SerpBear&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Raw self-hosted SERP infrastructure for scripts and agents&lt;/td&gt;
&lt;td&gt;OpenSERP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integrated Semrush-style product with DataForSEO underneath&lt;/td&gt;
&lt;td&gt;OpenSEO&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Recommended Stacks by Website Type
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Small WordPress blog
&lt;/h3&gt;

&lt;p&gt;A conventional WordPress SEO plugin plus occasional technical crawling is enough:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The SEO Framework / Yoast / SEOPress
+
SiteOne or LibreCrawl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is little reason to deploy five services for a small site.&lt;/p&gt;

&lt;h3&gt;
  
  
  Established WordPress publication
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WordPress SEO plugin
+
CrawlSEO or SEO Skill
+
Search Console
+
technical crawler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a local LLM only if you have enough content that automated analysis saves meaningful time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hugo or static technical site
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SiteOne
+
SEO Skill
+
Search Console
+
Git CI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes SEO part of the engineering workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI-heavy self-hosted environment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scouter or SiteOne
+
SEO Skill
+
MCP agent
+
local Ollama / llama.cpp / vLLM
+
Git review workflow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where self-hosting provides the most architectural freedom.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The open-source SEO ecosystem is large enough that "self-hosted SEO" is a real category rather than a collection of abandoned scripts. No single open-source application reproduces every useful feature of Ahrefs or Semrush without external data: the global keyword databases and web-scale backlink indexes remain expensive because the underlying data is expensive to collect.&lt;/p&gt;

&lt;p&gt;The practical opportunity is in the layers you can own: technical crawling, first-party Search Console analysis, local SERP observation at moderate scale, local LLM interpretation of that evidence, MCP as the interface to agents, and CI/CD as a regression gate. For most technically managed websites, that combination is more valuable than recreating a commercial suite, and it answers the question that matters: which capabilities do you need, which data can you collect yourself, and where does an external dataset genuinely add value.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/every-app/open-seo" rel="noopener noreferrer"&gt;OpenSEO&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/janreges/siteone-crawler" rel="noopener noreferrer"&gt;SiteOne Crawler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/PhialsBasement/LibreCrawl" rel="noopener noreferrer"&gt;LibreCrawl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/lokoe-mehdi/scouter" rel="noopener noreferrer"&gt;Scouter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/iannuttall/seo" rel="noopener noreferrer"&gt;SEO Skill&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/crawlseo/crawlseo" rel="noopener noreferrer"&gt;CrawlSEO&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fenjo26/opengsc" rel="noopener noreferrer"&gt;OpenGSC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/towfiqi/serpbear" rel="noopener noreferrer"&gt;SerpBear&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fredyglen/OpenSERP" rel="noopener noreferrer"&gt;OpenSERP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dataforseo.com/" rel="noopener noreferrer"&gt;DataForSEO&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>seo</category>
      <category>selfhosting</category>
      <category>opensource</category>
    </item>
    <item>
      <title>WordPress SEO Plugins Compared: Yoast to Local AI</title>
      <dc:creator>Rost</dc:creator>
      <pubDate>Sat, 03 Oct 2026 12:37:47 +0000</pubDate>
      <link>https://dev.to/rosgluk/wordpress-seo-plugins-compared-yoast-to-local-ai-2l33</link>
      <guid>https://dev.to/rosgluk/wordpress-seo-plugins-compared-yoast-to-local-ai-2l33</guid>
      <description>&lt;p&gt;WordPress SEO plugins add the metadata, sitemaps, schema, and AI layer above WordPress core, and in 2026 that layer includes plugins that run inference on your own hardware.&lt;/p&gt;

&lt;p&gt;The market is no longer a choice between Yoast and a few competitors. It now contains commercial SEO suites, lightweight open-source plugins, AI assistants that call hosted APIs, and newer plugins that talk to local models through Ollama, llama.cpp, or any OpenAI-compatible endpoint.&lt;/p&gt;

&lt;p&gt;The answer to "which WordPress SEO plugin should I use" depends on what you need: a complete marketing suite, a quiet technical SEO layer, or a local AI assistant operating alongside a conventional plugin. This article compares the three models, walks through the major commercial, open-source, and local-AI plugins, and places the plugin layer inside the wider &lt;a href="https://www.glukhov.org/web-infrastructure/" rel="noopener noreferrer"&gt;Web Infrastructure hub&lt;/a&gt; of this site.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a WordPress SEO Plugin Does
&lt;/h2&gt;

&lt;p&gt;A WordPress SEO plugin sits between WordPress content and the HTML, feeds, sitemaps, and machine-readable metadata consumed by search engines and social platforms.&lt;/p&gt;

&lt;p&gt;Its most important jobs are deterministic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;generate SEO titles and meta descriptions;&lt;/li&gt;
&lt;li&gt;define canonical URLs;&lt;/li&gt;
&lt;li&gt;control &lt;code&gt;index&lt;/code&gt;, &lt;code&gt;noindex&lt;/code&gt;, &lt;code&gt;follow&lt;/code&gt;, and &lt;code&gt;nofollow&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;create XML sitemaps;&lt;/li&gt;
&lt;li&gt;add Schema.org structured data;&lt;/li&gt;
&lt;li&gt;add Open Graph and social metadata;&lt;/li&gt;
&lt;li&gt;manage redirects and 404s;&lt;/li&gt;
&lt;li&gt;generate breadcrumbs;&lt;/li&gt;
&lt;li&gt;expose SEO fields in the editor;&lt;/li&gt;
&lt;li&gt;integrate with Google Search Console or analytics;&lt;/li&gt;
&lt;li&gt;provide content and internal-link analysis.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A conventional flow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A[WordPress content] --&amp;gt; B[SEO plugin]
    B --&amp;gt; C[Title and description]
    B --&amp;gt; D[Canonical and robots]
    B --&amp;gt; E[Schema JSON-LD]
    B --&amp;gt; F[XML sitemap]
    B --&amp;gt; G[Open Graph]
    C --&amp;gt; H[Published HTML]
    D --&amp;gt; H
    E --&amp;gt; H
    G --&amp;gt; H
    F --&amp;gt; I[Search engines]
    H --&amp;gt; I&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;A canonical URL should not depend on whether an LLM happens to make a good decision on a particular day. Sitemap output stays in the same deterministic layer; &lt;a href="https://www.glukhov.org/web-infrastructure/indexing/indexnow/" rel="noopener noreferrer"&gt;IndexNow&lt;/a&gt; handles the separate question of telling engines when URLs change.&lt;/p&gt;

&lt;p&gt;AI is useful for a different class of tasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;proposing alternative titles;&lt;/li&gt;
&lt;li&gt;rewriting descriptions;&lt;/li&gt;
&lt;li&gt;identifying missing sections;&lt;/li&gt;
&lt;li&gt;suggesting internal links;&lt;/li&gt;
&lt;li&gt;generating image alt text;&lt;/li&gt;
&lt;li&gt;clustering topics;&lt;/li&gt;
&lt;li&gt;summarizing SEO findings;&lt;/li&gt;
&lt;li&gt;assisting editors with content improvements.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That gives a second workflow:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A[Post or page] --&amp;gt; B[SEO analysis]
    B --&amp;gt; C[AI provider]
    C --&amp;gt; D[Suggested change]
    D --&amp;gt; E[Human review]
    E --&amp;gt; F[Approved update]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Deterministic output stays with the SEO plugin; language generation goes to the AI layer, and the two can live in the same plugin or in two separate ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three WordPress SEO Plugin Models
&lt;/h2&gt;

&lt;p&gt;Most WordPress SEO products now fall into one of three models.&lt;/p&gt;

&lt;h3&gt;
  
  
  Commercial SEO Suites
&lt;/h3&gt;

&lt;p&gt;Yoast SEO, Rank Math, All in One SEO, and SEOPress combine a free WordPress plugin with paid features, commercial services, or premium editions. Their main advantage is maturity: large user bases, established migration paths, compatibility with popular themes and page builders, and extensive documentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lightweight Self-Hosted SEO Plugins
&lt;/h3&gt;

&lt;p&gt;The SEO Framework and Slim SEO take a quieter approach. Their core SEO functionality runs inside WordPress and focuses on automatically generating correct metadata rather than turning SEO into a large marketing dashboard. They fit site owners who already understand technical SEO and do not need continuous scoring or editorial coaching.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI SEO Assistants
&lt;/h3&gt;

&lt;p&gt;A newer category adds generative AI to WordPress. Some plugins call OpenAI, Anthropic, Gemini, Groq, OpenRouter, or other hosted providers. Others talk to Ollama, llama.cpp, vLLM, LM Studio, or another self-hosted model endpoint.&lt;/p&gt;

&lt;p&gt;The distinction matters for privacy and cost: a plugin can be installed on your own WordPress server while still sending every article to an external AI service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Commercial WordPress SEO Plugins Compared
&lt;/h2&gt;

&lt;p&gt;The four dominant commercial or freemium products have substantially larger deployment histories than the newer AI-focused plugins.&lt;/p&gt;

&lt;p&gt;Active-installation figures below are approximate WordPress.org values as of September 2026.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plugin&lt;/th&gt;
&lt;th&gt;Active installs&lt;/th&gt;
&lt;th&gt;Main strength&lt;/th&gt;
&lt;th&gt;Complexity&lt;/th&gt;
&lt;th&gt;Best suited for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Yoast SEO&lt;/td&gt;
&lt;td&gt;10M+&lt;/td&gt;
&lt;td&gt;Mature defaults and editorial guidance&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;General WordPress sites&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rank Math&lt;/td&gt;
&lt;td&gt;4M+&lt;/td&gt;
&lt;td&gt;Broad feature set and detailed controls&lt;/td&gt;
&lt;td&gt;Medium-High&lt;/td&gt;
&lt;td&gt;Power users and feature-rich sites&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;All in One SEO&lt;/td&gt;
&lt;td&gt;2M+&lt;/td&gt;
&lt;td&gt;Complete SEO and marketing suite&lt;/td&gt;
&lt;td&gt;Medium-High&lt;/td&gt;
&lt;td&gt;Business and WooCommerce sites&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SEOPress&lt;/td&gt;
&lt;td&gt;300K+&lt;/td&gt;
&lt;td&gt;Technical controls and privacy-oriented design&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Developers and agencies&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Yoast SEO
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://yoast.com/wordpress/plugins/seo/" rel="noopener noreferrer"&gt;Yoast SEO&lt;/a&gt; is developed by Yoast and remains the most widely installed dedicated WordPress SEO plugin. WordPress.org reports more than 10 million active installations, which gives it an unusually large production footprint.&lt;/p&gt;

&lt;p&gt;Yoast covers the expected SEO fundamentals: titles, descriptions, canonical URLs, XML sitemaps, schema, breadcrumbs, robots controls, social metadata, readability analysis, and content optimization. Its strongest feature is not one specific algorithm but maturity: themes, hosts, migration tools, and other plugins generally understand Yoast metadata.&lt;/p&gt;

&lt;p&gt;The disadvantage is accumulated complexity. Yoast now includes a large amount of editorial guidance, commercial functionality, integrations, and AI-related features, which can feel excessive when all you need is a reliable metadata and sitemap layer.&lt;/p&gt;

&lt;p&gt;Installation is simple and its setup flow suits non-specialists. Choose Yoast when compatibility, documentation, and proven behavior matter more than minimalism.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rank Math
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://rankmath.com/" rel="noopener noreferrer"&gt;Rank Math&lt;/a&gt; has grown into the largest direct alternative to Yoast, with more than 4 million active WordPress installations. It takes a more feature-heavy approach and exposes many controls that other products either hide or split into separate modules.&lt;/p&gt;

&lt;p&gt;Rank Math includes titles, descriptions, XML sitemaps, schema, redirection management, 404 monitoring, Search Console-related features, content analysis, and AI-oriented tooling. Its LLMS Txt module, toggled from the Rank Math dashboard, generates and maintains an &lt;code&gt;llms.txt&lt;/code&gt; file at the site root that points AI crawlers at the pages that matter most; other suites added similar generation during 2025, so check the exact edition and version before relying on it.&lt;/p&gt;

&lt;p&gt;In 2026 Rank Math added MCP functionality. The MCP server exposes tools for running SEO audits, analyzing posts, reviewing metadata scores and Schema markup, checking links and redirects, retrieving Search Console keyword data with impressions, position, and CTR, fetching &lt;code&gt;robots.txt&lt;/code&gt; and &lt;code&gt;llms.txt&lt;/code&gt;, and updating supported settings. The vendor documents client support for MCP-capable assistants such as Claude Desktop and GitHub Copilot, so an external agent can read the plugin's SEO state without a browser session.&lt;/p&gt;

&lt;p&gt;Its strength is breadth, and that breadth creates complexity. A small publication may not need dozens of modules, scores, dashboards, and integrations, and enabling too much functionality turns the SEO plugin into another administration platform that needs ongoing maintenance.&lt;/p&gt;

&lt;p&gt;Installation is still straightforward, but configuring Rank Math well requires more decisions than installing a lightweight plugin. Choose Rank Math when you want most SEO functions concentrated in one product and do not mind a larger configuration surface.&lt;/p&gt;

&lt;h3&gt;
  
  
  All in One SEO
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://aioseo.com/" rel="noopener noreferrer"&gt;All in One SEO&lt;/a&gt;, commonly called AIOSEO, is one of the longest-running WordPress SEO products. WordPress.org reports more than 2 million active installations.&lt;/p&gt;

&lt;p&gt;AIOSEO combines titles, descriptions, canonical controls, XML sitemaps, schema, redirects, local SEO, WooCommerce SEO, Search Console integration, internal-link assistance, author SEO, rank-related features, and AI content tools. The product increasingly resembles a complete SEO and marketing platform rather than a narrow technical plugin.&lt;/p&gt;

&lt;p&gt;That makes it useful for commercial sites and less compelling for a technically managed publication that prefers small components. Many of the more advanced features belong to the paid product, so the useful feature boundary depends on the edition in use.&lt;/p&gt;

&lt;p&gt;The setup wizard is approachable, but the full product has a substantial feature surface. Choose AIOSEO when you want a broad business-oriented SEO suite, particularly for WooCommerce or sites where marketing staff manage SEO directly in WordPress.&lt;/p&gt;

&lt;h3&gt;
  
  
  SEOPress
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.seopress.org/" rel="noopener noreferrer"&gt;SEOPress&lt;/a&gt; is a mature alternative developed by the SEOPress team, with more than 300,000 active installations. It appeals to developers and agencies because it combines strong technical controls with a comparatively restrained interface.&lt;/p&gt;

&lt;p&gt;SEOPress supports titles, descriptions, canonical URLs, XML and HTML sitemaps, Open Graph, analytics integration, structured data, redirects, content analysis, &lt;code&gt;llms.txt&lt;/code&gt;, and AI-search-oriented functionality. It also provides hooks, REST support, WP-CLI integration, and white-label options. Its PRO edition adds AI metadata and image alt-text generation through hosted providers (GPT, Claude, Mistral, DeepSeek, and Gemini), which keeps the local-inference question to the plugins in the later section.&lt;/p&gt;

&lt;p&gt;Compared with Yoast and AIOSEO, SEOPress feels less focused on coaching the editor through every paragraph. The tradeoff is that many advanced functions are part of the commercial edition, so a site owner should compare the exact edition rather than assume every advertised feature belongs to the free plugin.&lt;/p&gt;

&lt;p&gt;Configuration complexity is moderate. Choose SEOPress when you want a mature general-purpose SEO suite with stronger developer controls and less intrusive branding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lightweight SEO Plugins: The SEO Framework and Slim SEO
&lt;/h2&gt;

&lt;p&gt;Not every site needs a large SEO suite. For many technical publishers, the ideal SEO plugin generates correct metadata, stays predictable, and stays out of the way. The SEO Framework and Slim SEO are the two strongest examples of that philosophy.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plugin&lt;/th&gt;
&lt;th&gt;Active installs&lt;/th&gt;
&lt;th&gt;GitHub stars&lt;/th&gt;
&lt;th&gt;GitHub forks&lt;/th&gt;
&lt;th&gt;Main strength&lt;/th&gt;
&lt;th&gt;Complexity&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The SEO Framework&lt;/td&gt;
&lt;td&gt;200K+&lt;/td&gt;
&lt;td&gt;480+&lt;/td&gt;
&lt;td&gt;60+&lt;/td&gt;
&lt;td&gt;Quiet, deterministic SEO&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slim SEO&lt;/td&gt;
&lt;td&gt;70K+&lt;/td&gt;
&lt;td&gt;90+&lt;/td&gt;
&lt;td&gt;20+&lt;/td&gt;
&lt;td&gt;Automatic configuration&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;GitHub numbers should be interpreted carefully for WordPress plugins. WordPress.org remains the main installation and update channel, so active installations are usually a stronger adoption signal than stars alone.&lt;/p&gt;

&lt;h3&gt;
  
  
  The SEO Framework
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://theseoframework.com/" rel="noopener noreferrer"&gt;The SEO Framework&lt;/a&gt; is developed by Sybre Waaijer and CyberWire. Its core plugin is released under GPLv3 and has more than 200,000 active WordPress installations.&lt;/p&gt;

&lt;p&gt;It automatically handles titles, descriptions, canonical metadata, Open Graph, structured data, robots directives, and XML sitemaps. Its main advantage is restraint: it tries to produce sensible SEO output without filling the editor with keyword scores, traffic-light indicators, or a long list of upsells.&lt;/p&gt;

&lt;p&gt;The SEO Framework is not an AI SEO platform. That means local LLM functionality has to come from another plugin or integration.&lt;/p&gt;

&lt;p&gt;Installation is simple and default behavior is sensible for many sites. Choose The SEO Framework when you want a reliable technical SEO layer that can later be combined with a separate AI assistant.&lt;/p&gt;

&lt;h3&gt;
  
  
  Slim SEO
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://wpslimseo.com/" rel="noopener noreferrer"&gt;Slim SEO&lt;/a&gt; is developed by eLightUp and has more than 70,000 active WordPress installations. Its source is publicly available on GitHub and the project emphasizes automatic configuration.&lt;/p&gt;

&lt;p&gt;Slim SEO generates titles, descriptions, Open Graph metadata, XML sitemaps, schema, breadcrumbs, and related metadata with relatively little configuration. Recent releases added AI-generated meta titles and descriptions through providers including OpenAI, Anthropic, Gemini, and OpenRouter.&lt;/p&gt;

&lt;p&gt;Its weakness for privacy-focused self-hosting is that the built-in AI path is oriented toward hosted providers rather than a generic local OpenAI-compatible server. The core SEO engine remains local, but AI usage is not automatically fully self-hosted.&lt;/p&gt;

&lt;p&gt;Installation is one of the simplest in this comparison. Choose Slim SEO when you prefer automation over configuration and do not need a large SEO administration suite.&lt;/p&gt;

&lt;h2&gt;
  
  
  WordPress Plugins with Local LLM Support
&lt;/h2&gt;

&lt;p&gt;This is where the market becomes more experimental. The important question is no longer simply whether the WordPress plugin is open source. You need to ask where the inference runs and whether a vendor backend sits between WordPress and the model.&lt;/p&gt;

&lt;p&gt;A useful classification:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plugin&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Local/self-hosted LLM&lt;/th&gt;
&lt;th&gt;Vendor backend required&lt;/th&gt;
&lt;th&gt;Maturity&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AI Engine&lt;/td&gt;
&lt;td&gt;General AI framework&lt;/td&gt;
&lt;td&gt;Yes, OpenAI-compatible&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Established&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Coretex SEO&lt;/td&gt;
&lt;td&gt;AI-assisted SEO&lt;/td&gt;
&lt;td&gt;Yes, Ollama&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;New&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SEO Auditor Tools&lt;/td&gt;
&lt;td&gt;SEO audit + AI fixes&lt;/td&gt;
&lt;td&gt;Yes, Ollama&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;New&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PIV AI SEO Assistant&lt;/td&gt;
&lt;td&gt;Metadata and image alt generation&lt;/td&gt;
&lt;td&gt;Yes, OpenAI-compatible&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;New&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Synthocode Content Copilot&lt;/td&gt;
&lt;td&gt;Editor AI assistant&lt;/td&gt;
&lt;td&gt;Yes, Ollama&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;New&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The maturity column matters: several local-AI SEO plugins are promising architecturally but have tiny WordPress.org user bases compared with the established SEO plugins.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI Engine
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://wordpress.org/plugins/ai-engine/" rel="noopener noreferrer"&gt;AI Engine&lt;/a&gt; is developed by Meow Apps and has around 90,000 active installations. It is not primarily an SEO plugin; it is an AI framework for WordPress.&lt;/p&gt;

&lt;p&gt;Its main strength is provider flexibility. AI Engine supports hosted providers but also accepts custom OpenAI-compatible endpoints such as Ollama, LM Studio, vLLM, llama.cpp, and LocalAI. It also provides developer APIs, hooks, and MCP functionality.&lt;/p&gt;

&lt;p&gt;That makes AI Engine interesting when you want to keep deterministic SEO in The SEO Framework, SEOPress, or another conventional plugin while adding local AI workflows separately. It can become the connection between WordPress and a private inference server.&lt;/p&gt;

&lt;p&gt;The downside is that it does not automatically replace a dedicated SEO plugin. You design or configure the actual SEO workflow yourself, and some advanced AI Engine functionality belongs to commercial modules.&lt;/p&gt;

&lt;p&gt;Choose AI Engine when you want a programmable local AI layer rather than a narrow "generate meta description" button.&lt;/p&gt;

&lt;p&gt;A typical architecture looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A[WordPress] --&amp;gt; B[The SEO Framework or SEOPress]
    A --&amp;gt; C[AI Engine]
    C --&amp;gt; D[OpenAI-compatible endpoint]
    D --&amp;gt; E[llama.cpp / Ollama / vLLM]
    E --&amp;gt; F[Local LLM]&lt;/code&gt;&lt;/pre&gt;



&lt;h3&gt;
  
  
  Coretex SEO
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://wordpress.org/plugins/coretex-seo/" rel="noopener noreferrer"&gt;Coretex SEO&lt;/a&gt; is a newer AI-assisted SEO plugin built around recommendations rather than automatic modification.&lt;/p&gt;

&lt;p&gt;It can analyze titles, descriptions, headings, internal links, and image SEO, then place proposed changes into an approval queue with a before/after preview. Nothing is written to the site until an administrator accepts the suggestion. It supports commercial AI providers as well as a local Ollama instance, and the plugin documentation states that no Coretex account or Coretex-hosted backend is required.&lt;/p&gt;

&lt;p&gt;The approval workflow is one of its strongest design choices: AI-generated SEO changes remain suggestions, and the model has no standing authority to modify pages.&lt;/p&gt;

&lt;p&gt;The main concern is maturity. Its active-installation count is still very small, so it does not have the deployment history of Yoast, Rank Math, The SEO Framework, or AI Engine.&lt;/p&gt;

&lt;p&gt;Choose Coretex when local Ollama integration and review-first AI suggestions matter more than a long production track record.&lt;/p&gt;

&lt;h3&gt;
  
  
  SEO Auditor Tools
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://wordpress.org/plugins/seo-auditor-tools/" rel="noopener noreferrer"&gt;SEO Auditor Tools&lt;/a&gt; combines a WordPress SEO audit with a large collection of additional site-management utilities.&lt;/p&gt;

&lt;p&gt;Its audit layer covers SEO, accessibility, performance, and best practices. It also includes sitemap and robots tooling, redirects, image tools, &lt;code&gt;llms.txt&lt;/code&gt;, and an AI helper that can connect to a self-hosted Ollama server.&lt;/p&gt;

&lt;p&gt;The AI workflow is conservative: it generates structured proposals, validates them, shows a preview, and requires administrator approval before writing changes. It also records changes and supports undo.&lt;/p&gt;

&lt;p&gt;The downside is scope. The plugin bundles SEO, performance, security, analytics, image tools, redirects, and other functions in one package, which is a lot of responsibility for a relatively new plugin.&lt;/p&gt;

&lt;p&gt;Choose it if you want an all-in-one local audit and remediation tool and are comfortable evaluating a younger project carefully.&lt;/p&gt;

&lt;h3&gt;
  
  
  PIV AI SEO Assistant
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://wordpress.org/plugins/piv-ai-seo-assistant/" rel="noopener noreferrer"&gt;AI SEO Meta and Image Alt Text Generator&lt;/a&gt; is a focused helper rather than a complete SEO engine.&lt;/p&gt;

&lt;p&gt;Its most important feature for self-hosting is support for arbitrary OpenAI-compatible endpoints. That allows it to work with Ollama, LM Studio, private gateways, and other compatible servers without forcing content through a vendor-owned AI backend.&lt;/p&gt;

&lt;p&gt;The plugin focuses on titles, descriptions, keywords, social metadata, and image alt text. It can write into metadata managed by established plugins such as Yoast, Rank Math, AIOSEO, SEOPress, and The SEO Framework.&lt;/p&gt;

&lt;p&gt;It does not replace those plugins. Canonical URLs, schema, sitemap generation, and other deterministic SEO functions still belong to the underlying SEO engine.&lt;/p&gt;

&lt;p&gt;Choose PIV when you already have an SEO plugin you trust and want to add local AI-assisted metadata generation on top.&lt;/p&gt;

&lt;h3&gt;
  
  
  Synthocode Content Copilot
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://wordpress.org/plugins/synthocode-content-copilot/" rel="noopener noreferrer"&gt;Synthocode Content Copilot&lt;/a&gt; is an editor-side writing assistant for Gutenberg.&lt;/p&gt;

&lt;p&gt;It can generate titles, descriptions, article ideas, summaries, tags, translations, and FAQ schema. It supports hosted providers as well as local Ollama, allowing content generation to remain inside your own infrastructure.&lt;/p&gt;

&lt;p&gt;Its advantage is simplicity: instead of becoming a full technical SEO platform, it assists the person writing or updating a post.&lt;/p&gt;

&lt;p&gt;That is also its limitation. It does not replace a proper SEO plugin for canonicals, sitemaps, redirects, schema management, or site-wide technical controls.&lt;/p&gt;

&lt;p&gt;Choose it when you mainly want a private AI assistant inside Gutenberg.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Does Inference Run? Four "Self-Hosted" Architectures
&lt;/h2&gt;

&lt;p&gt;The term "self-hosted" is used too loosely for WordPress SEO plugins. There are at least four distinct architectures.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Plugin installed locally, vendor service required
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WordPress
    |
    v
Plugin
    |
    v
Vendor cloud
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The plugin code runs on your server, but important functionality depends on the vendor.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Plugin installed locally, direct cloud API
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WordPress
    |
    v
Plugin
    |
    v
OpenAI / Anthropic / Gemini / other API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no plugin-vendor backend, but content still leaves your infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Plugin installed locally, local model
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WordPress
    |
    v
Plugin
    |
    v
Ollama / llama.cpp / vLLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is genuinely local inference.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Deterministic SEO plugin plus separate local AI
&lt;/h3&gt;



&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A[WordPress] --&amp;gt; B[SEO plugin]
    B --&amp;gt; C[Metadata / schema / sitemap]
    A --&amp;gt; D[AI integration]
    D --&amp;gt; E[Local LLM]
    E --&amp;gt; F[Suggestions]
    F --&amp;gt; G[Human review]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;For technically managed sites, the fourth model is usually the most attractive. It keeps critical SEO infrastructure independent of model availability and separates deterministic decisions from language-generation tasks. The same data-residency argument applies at the workflow level, not just the model level, as the &lt;a href="https://www.glukhov.org/llm-hosting/self-hosting/data-gravity-ai-vendor-lockin/" rel="noopener noreferrer"&gt;data gravity and API lock-in&lt;/a&gt; analysis shows. For choosing between Ollama, llama.cpp, vLLM, LM Studio, and similar servers, the &lt;a href="https://www.glukhov.org/llm-hosting/comparisons/hosting-llms-ollama-localai-jan-lmstudio-vllm-comparison/" rel="noopener noreferrer"&gt;local LLM hosting comparison&lt;/a&gt; covers API support, hardware compatibility, and production readiness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pointing a WordPress Plugin at a Local Endpoint
&lt;/h2&gt;

&lt;p&gt;Whichever plugin in the previous section you choose, the configuration reduces to one thing: an OpenAI-compatible base URL. Working through the checks below with curl first tells you whether a later problem is in the plugin or in the model server.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start the model server and make sure a model is pulled. For Ollama:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   ollama serve
   ollama pull llama3.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://www.glukhov.org/llm-hosting/ollama/ollama-cheatsheet/" rel="noopener noreferrer"&gt;Ollama CLI cheatsheet&lt;/a&gt; covers the full command set.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Verify that the OpenAI-compatible endpoint answers:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   curl http://127.0.0.1:11434/v1/models
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response lists pulled models. An empty list or a connection error means the server is not ready, not that the plugin is broken.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Send one completion in the same shape the plugin will send:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   curl http://127.0.0.1:11434/v1/chat/completions &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"model":"llama3.2","messages":[{"role":"user","content":"Write a 155-character meta description for a post about Hugo sitemaps."}]}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Enter the base URL in the plugin's AI settings. The defaults for common runtimes:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;| Runtime | Local base URL |&lt;br&gt;
   |---|---|&lt;br&gt;
   | Ollama | &lt;code&gt;http://127.0.0.1:11434/v1&lt;/code&gt; |&lt;br&gt;
   | LM Studio | &lt;code&gt;http://127.0.0.1:1234/v1&lt;/code&gt; |&lt;br&gt;
   | vLLM | &lt;code&gt;http://127.0.0.1:8000/v1&lt;/code&gt; |&lt;br&gt;
   | llama.cpp (&lt;code&gt;llama-server&lt;/code&gt;) | &lt;code&gt;http://127.0.0.1:8080/v1&lt;/code&gt; |&lt;/p&gt;

&lt;p&gt;Most of these servers need no authentication locally; when the plugin demands an API key, any non-empty string (for example &lt;code&gt;ollama&lt;/code&gt;) satisfies the field.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run the plugin's connection test, generate a single description, then inspect the rendered page's &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; to confirm the meta tag actually changed. If the test fails while the curl calls succeed, check that the base URL includes the &lt;code&gt;/v1&lt;/code&gt; suffix and that the model name matches the output of &lt;code&gt;ollama list&lt;/code&gt; exactly.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Two security checks belong in the same pass:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ollama binds to &lt;code&gt;127.0.0.1&lt;/code&gt; by default. If WordPress runs on the same host, leave it that way. Setting &lt;code&gt;OLLAMA_HOST=0.0.0.0:11434&lt;/code&gt; exposes an unauthenticated inference endpoint to the network, where anyone can use your GPU and the models you loaded. If the plugin must reach the server from another machine, the Tailscale or WireGuard path in &lt;a href="https://www.glukhov.org/llm-hosting/ollama/ollama-remote-access/" rel="noopener noreferrer"&gt;remote Ollama access&lt;/a&gt; replaces opening a public port.&lt;/li&gt;
&lt;li&gt;On the WordPress side, a local endpoint requires lifting WordPress's default block on HTTP requests to private addresses. The connector plugins that do this restrict the exception to the endpoint you configure, so other plugin HTTP calls cannot be pointed at internal addresses. Before enabling a local provider, check which hosts and ports your connector allowlists.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Which Plugin Combination Makes Sense?
&lt;/h2&gt;

&lt;p&gt;For many sites, the best answer is not one plugin.&lt;/p&gt;
&lt;h3&gt;
  
  
  Conventional blog or small business site
&lt;/h3&gt;

&lt;p&gt;Use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Yoast
or
Rank Math
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both have large ecosystems and require little custom engineering. Yoast is the more conservative choice. Rank Math suits users who want more features and fine-grained controls in one place.&lt;/p&gt;

&lt;h3&gt;
  
  
  Developer-managed WordPress site
&lt;/h3&gt;

&lt;p&gt;Use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The SEO Framework
or
SEOPress
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SEO Framework is particularly attractive if you want the SEO layer to be small and predictable. SEOPress makes more sense if you want richer built-in controls without moving to the largest commercial suites.&lt;/p&gt;

&lt;h3&gt;
  
  
  Privacy-focused site using a local LLM
&lt;/h3&gt;

&lt;p&gt;A clean architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The SEO Framework
+
AI Engine or PIV
+
llama.cpp / Ollama / vLLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separates responsibilities:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The SEO Framework:
  canonical URLs
  robots
  schema
  sitemap
  social metadata

AI layer:
  title suggestions
  descriptions
  content review
  alt text
  editorial assistance

Local LLM:
  inference
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That architecture is easier to reason about than letting one AI-heavy plugin control every SEO function.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gutenberg-focused writer
&lt;/h3&gt;

&lt;p&gt;Use a conventional SEO plugin plus Synthocode if the main requirement is editor assistance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Experimental local-AI SEO workflow
&lt;/h3&gt;

&lt;p&gt;Coretex SEO or SEO Auditor Tools are interesting, particularly because both use approval-oriented workflows. Their small deployment bases mean they should be evaluated more carefully before being made responsible for important production metadata.&lt;/p&gt;

&lt;h2&gt;
  
  
  What WordPress SEO Plugins Do Not Solve
&lt;/h2&gt;

&lt;p&gt;Even the largest WordPress SEO plugins operate mostly inside one website. They can optimize metadata, site structure, schema, content, internal links, and redirects.&lt;/p&gt;

&lt;p&gt;They cannot independently create the large external datasets provided by products such as Ahrefs or Semrush. A WordPress plugin cannot cheaply reproduce a web-scale backlink index or a global keyword-volume database. When a plugin offers those features, the data usually comes from the vendor or another external API.&lt;/p&gt;

&lt;p&gt;The broader distinction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WordPress SEO plugin:
  controls and optimizes the website

SEO platform:
  observes the website in the wider search ecosystem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A serious self-hosted SEO platform belongs outside WordPress. Technical crawlers, Search Console analysis, local rank tracking, MCP tools, and local LLM analysis can all be self-hosted separately; the &lt;a href="https://www.glukhov.org/web-infrastructure/seo/self-hosted-seo-tools-and-platforms/" rel="noopener noreferrer"&gt;self-hosted SEO tools and platforms comparison&lt;/a&gt; covers that layer in detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Should Not Become the Source of Truth
&lt;/h2&gt;

&lt;p&gt;Generative AI is useful for SEO, but it should not determine facts that ordinary software can measure.&lt;/p&gt;

&lt;p&gt;Bad architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM decides:
  whether page is indexed
  whether canonical is correct
  current ranking
  number of broken links
  Core Web Vitals
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Better architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;crawler / Search Console / WordPress:
  collect facts

LLM:
  interpret facts
  suggest improvements
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FACT:
  page ranks around position 7

FACT:
  page has high impressions

FACT:
  CTR is unusually low

FACT:
  title is generic

AI TASK:
  propose three titles that better match the dominant queries
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Proposing titles from collected facts is a safer use of an LLM than asking it to perform an SEO audit from memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  WordPress SEO in 2026: What Has Changed
&lt;/h2&gt;

&lt;p&gt;The most visible change is not that SEO plugins acquired more AI buttons. It is that WordPress can connect to AI systems through standard interfaces.&lt;/p&gt;

&lt;p&gt;WordPress 7.0 introduced the AI Client, which exposes AI providers through a connector system visible under Settings → Connectors. Two connectors target self-hosted inference directly: &lt;a href="https://wordpress.org/plugins/ai-provider-for-ollama/" rel="noopener noreferrer"&gt;AI Provider for Ollama&lt;/a&gt; registers Ollama as a provider and handles the localhost allowlisting, port registration, and keyless local authentication, while rtCamp's &lt;a href="https://github.com/rtcamp/universal-openai-connector" rel="noopener noreferrer"&gt;Universal OpenAI Connector&lt;/a&gt; registers an &lt;code&gt;openai_compatible&lt;/code&gt; provider that accepts any compatible endpoint, including LM Studio and private gateways. Custom connectors must allowlist localhost hosts and safe ports themselves; without that, requests from the AI Client to a local server fail silently.&lt;/p&gt;

&lt;p&gt;AI Engine supports arbitrary OpenAI-compatible endpoints including Ollama, LM Studio, vLLM, llama.cpp, and LocalAI. There is also a separate &lt;a href="https://wordpress.org/plugins/ai-provider-for-openai-compatible-servers/" rel="noopener noreferrer"&gt;AI Provider for OpenAI Compatible Servers&lt;/a&gt; plugin for the WordPress AI Client, designed to connect WordPress to self-hosted inference servers. Either path keeps a local model behind the WordPress installation:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A[WordPress] --&amp;gt; B[AI integration]
    B --&amp;gt; C[OpenAI-compatible API]
    C --&amp;gt; D[llama.cpp]
    D --&amp;gt; E[Local model]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Products such as Rank Math and AI Engine are adding MCP support. That points toward AI agents interacting with WordPress and SEO tools through controlled interfaces rather than through embedded plugin screens. The &lt;a href="https://www.glukhov.org/ai-systems/mcp/mcp-server-in-go/" rel="noopener noreferrer"&gt;MCP server implementation notes in Go&lt;/a&gt; show what that server side looks like.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;There is no single best WordPress SEO plugin because the products now solve different problems. The decision is an architectural one: keep canonical URLs, sitemaps, schema, and indexing rules in deterministic code you can audit, verify the local endpoint with plain curl before trusting a plugin's AI settings, and use a model only where language reasoning adds value, with generated changes passing human review before publication.&lt;/p&gt;

&lt;p&gt;If the goal grows beyond one plugin and reaches parts of Semrush, Ahrefs, or another external SEO platform, the architecture has to move outside WordPress entirely, into the self-hosted crawler, Search Console, and local LLM layer described in the companion platform comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://yoast.com/wordpress/plugins/seo/" rel="noopener noreferrer"&gt;Yoast SEO&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rankmath.com/" rel="noopener noreferrer"&gt;Rank Math&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rankmath.com/blog/rank-math-mcp" rel="noopener noreferrer"&gt;Rank Math MCP: AI-powered SEO automation for WordPress&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rankmath.com/kb/llms-txt" rel="noopener noreferrer"&gt;How to use llms.txt in Rank Math&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aioseo.com/" rel="noopener noreferrer"&gt;All in One SEO&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.seopress.org/" rel="noopener noreferrer"&gt;SEOPress&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.seopress.org/features/openai" rel="noopener noreferrer"&gt;SEOPress AI features&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://theseoframework.com/" rel="noopener noreferrer"&gt;The SEO Framework&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wpslimseo.com/" rel="noopener noreferrer"&gt;Slim SEO&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wordpress.org/plugins/ai-engine/" rel="noopener noreferrer"&gt;AI Engine (WordPress.org)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wordpress.org/plugins/coretex-seo/" rel="noopener noreferrer"&gt;Coretex SEO (WordPress.org)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wordpress.org/plugins/seo-auditor-tools/" rel="noopener noreferrer"&gt;SEO Auditor Tools (WordPress.org)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wordpress.org/plugins/piv-ai-seo-assistant/" rel="noopener noreferrer"&gt;PIV AI SEO Assistant (WordPress.org)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wordpress.org/plugins/synthocode-content-copilot/" rel="noopener noreferrer"&gt;Synthocode Content Copilot (WordPress.org)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wordpress.org/plugins/ai-provider-for-openai-compatible-servers/" rel="noopener noreferrer"&gt;AI Provider for OpenAI Compatible Servers (WordPress.org)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wordpress.org/plugins/ai-provider-for-ollama/" rel="noopener noreferrer"&gt;AI Provider for Ollama (WordPress.org)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rtcamp/universal-openai-connector" rel="noopener noreferrer"&gt;rtCamp Universal OpenAI Connector&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ollama.com/blog/openai-compatibility" rel="noopener noreferrer"&gt;Ollama: OpenAI compatibility&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>seo</category>
      <category>selfhosting</category>
      <category>ollama</category>
    </item>
    <item>
      <title>gstack: AI Software Engineering Stack</title>
      <dc:creator>Rost</dc:creator>
      <pubDate>Mon, 28 Sep 2026 05:33:00 +0000</pubDate>
      <link>https://dev.to/rosgluk/gstack-ai-software-engineering-stack-49ja</link>
      <guid>https://dev.to/rosgluk/gstack-ai-software-engineering-stack-49ja</guid>
      <description>&lt;p&gt;AI coding agents can already write functions, modify repositories, run tests, and open pull requests. The harder problem is getting an agent to follow a repeatable engineering process before, during, and after the code is written.&lt;/p&gt;

&lt;p&gt;gstack, a project from Garry Tan originally built around Claude Code, takes a different route: instead of replacing your coding agent with another platform, it wraps the agent you already use with specialized skills, browser tooling, reviews, safety controls, and release processes. The project describes the result as a virtual engineering team -- twenty-three specialists and eight power tools, all slash commands, all Markdown, MIT licensed.&lt;/p&gt;

&lt;p&gt;The comparison targets depend on which part of gstack you need: skill collections such as Superpowers, specification systems such as OpenSpec and GitHub Spec Kit, methodologies such as BMAD, orchestration platforms such as Ruflo, or your own maintained set of agent skills. Several of these combine with gstack rather than replace it, and the wider ecosystem they all belong to is mapped in the &lt;a href="https://www.glukhov.org/ai-devtools/" rel="noopener noreferrer"&gt;AI Developer Tools hub&lt;/a&gt; of this site.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is gstack?
&lt;/h2&gt;

&lt;p&gt;gstack is an open-source collection of AI engineering workflows. In gstack's framing, software development consists of several different kinds of reasoning, and asking one generic coding prompt to perform all of them is a poor abstraction -- so the project exposes specialized skills instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;product exploration&lt;/li&gt;
&lt;li&gt;product and CEO review&lt;/li&gt;
&lt;li&gt;architecture review&lt;/li&gt;
&lt;li&gt;developer experience review&lt;/li&gt;
&lt;li&gt;design review&lt;/li&gt;
&lt;li&gt;implementation review&lt;/li&gt;
&lt;li&gt;browser-based QA&lt;/li&gt;
&lt;li&gt;investigation and debugging&lt;/li&gt;
&lt;li&gt;security analysis&lt;/li&gt;
&lt;li&gt;documentation&lt;/li&gt;
&lt;li&gt;benchmarking&lt;/li&gt;
&lt;li&gt;release preparation&lt;/li&gt;
&lt;li&gt;deployment&lt;/li&gt;
&lt;li&gt;retrospectives&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project presents these roles as a virtual engineering team: a CEO who rethinks the product, an eng manager who locks architecture, a designer who catches "AI slop", a reviewer who finds production bugs, a QA lead who opens a real browser, a security officer who runs OWASP and STRIDE audits, and a release engineer who ships the PR. The toolchain around the skill definitions is TypeScript and Bun: a setup script, generated skill documentation, session hooks, state under &lt;code&gt;~/.gstack/&lt;/code&gt;, a bundled browser, and a set of standalone CLIs.&lt;/p&gt;

&lt;p&gt;gstack is not a foundation model and not a replacement for Claude Code; it is a process layer running on top of an agent harness:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    A[LLM] --&amp;gt; B[Claude Code or another supported harness]
    B --&amp;gt; C[gstack skills and workflow rules]
    subgraph G[gstack process stages]
        D1[Planning]
        D2[Architecture review]
        D3[Design review]
        D4[Code review]
        D5[Browser QA]
        D6[Security]
        D7[Release and deployment]
        D8[Learning and memory]
    end
    C --&amp;gt; G
    G --&amp;gt; E[Git repository, browser, and development tools]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Two structural properties decide where gstack fits. First, the project describes it as a process rather than a collection of tools: the skills run in the order a sprint runs -- think, plan, build, review, test, ship, reflect -- and each skill hands its artifacts to the next, so &lt;code&gt;/office-hours&lt;/code&gt; writes a design doc that &lt;code&gt;/plan-ceo-review&lt;/code&gt; reads, and &lt;code&gt;/plan-eng-review&lt;/code&gt; writes a test plan that &lt;code&gt;/qa&lt;/code&gt; picks up. Second, gstack is not Claude Code-only: &lt;code&gt;./setup&lt;/code&gt; auto-detects the agents installed on the machine, and &lt;code&gt;./setup --host &amp;lt;name&amp;gt;&lt;/code&gt; targets Codex CLI, OpenCode, Cursor, Factory Droid, Kiro, Slate, OpenClaw, and Hermes, while a 2KB instruction-only digest in the repo covers rules-reading agents that need no install at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why gstack Exists
&lt;/h2&gt;

&lt;p&gt;A blank Claude Code session is extremely flexible, and that flexibility is also one of its weaknesses. Consider a feature request such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Add organization-level API tokens to the application.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A capable agent might immediately inspect the repository and start modifying authentication code, whereas a senior engineer would first ask who owns the tokens, whether users can belong to multiple organizations, how tokens are revoked, whether permissions are inherited, what happens to existing authentication, whether tokens should expire, how secrets are displayed, and which audit events are required. The agent may discover some of these questions eventually, but there is no guarantee it will discover them before implementation. gstack moves that discipline into reusable workflows: instead of &lt;code&gt;idea -&amp;gt; coding agent -&amp;gt; code&lt;/code&gt;, the change passes product review, technical planning, architecture review, implementation, code review, browser QA, and release as named stages, each with its own command (the full sequence is in A Practical gstack Workflow below).&lt;/p&gt;

&lt;p&gt;This does not make the AI correct; it changes the probability distribution of its mistakes. The agent is pushed to challenge assumptions earlier, inspect evidence, review its own work from several perspectives, and verify the application instead of stopping when the code compiles.&lt;/p&gt;

&lt;h2&gt;
  
  
  How gstack Turns Markdown Skills into Process
&lt;/h2&gt;

&lt;p&gt;Most of gstack's capabilities originate in Markdown skill definitions. An agent skill can describe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;when it should run&lt;/li&gt;
&lt;li&gt;what context it should inspect&lt;/li&gt;
&lt;li&gt;what questions it should ask&lt;/li&gt;
&lt;li&gt;which tools it may use&lt;/li&gt;
&lt;li&gt;which commands it should execute&lt;/li&gt;
&lt;li&gt;which evidence it must gather&lt;/li&gt;
&lt;li&gt;which checks must pass&lt;/li&gt;
&lt;li&gt;how the result should be structured&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Such a skill acts somewhere between documentation, a reusable prompt, a standard operating procedure, and executable workflow configuration. The underlying mechanics are covered in &lt;a href="https://www.glukhov.org/ai-devtools/claude-code/claude-skills-for-developers/" rel="noopener noreferrer"&gt;Claude Skills and SKILL.md for Developers&lt;/a&gt;; gstack adds infrastructure on top of that primitive: generated skill definitions, startup and completion hooks, state management under &lt;code&gt;~/.gstack/&lt;/code&gt;, browser automation, safety mechanisms, repository inspection, opt-in telemetry, cross-session memory managed by &lt;code&gt;/learn&lt;/code&gt;, and optional persistent knowledge through the separate GBrain project, which &lt;code&gt;/setup-gbrain&lt;/code&gt; can stand up as a local PGLite database, a Supabase project, or a remote MCP endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Most Important gstack Skills
&lt;/h2&gt;

&lt;p&gt;The exact collection changes quickly, but several workflows illustrate how the system is intended to be used.&lt;/p&gt;

&lt;h3&gt;
  
  
  office-hours
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;/office-hours&lt;/code&gt; belongs near the beginning of a project or feature. It runs six forcing questions about the problem before any code is written, and in the README's worked example it reframes a request for a "daily briefing app" into a personal chief-of-staff AI, then writes the design doc that every downstream skill reads. For vague input like "we need better project search", the output is a requirement, not code.&lt;/p&gt;

&lt;h3&gt;
  
  
  plan-ceo-review
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;/plan-ceo-review&lt;/code&gt; examines the product-level assumptions behind a plan. It works in four scope modes -- Expansion, Selective Expansion, Hold Scope, Reduction -- and can challenge scope, identify missing opportunities, reduce unnecessary work, or suggest framing the problem differently. It runs before the requirements are fixed, a stage most coding-agent tools do not have.&lt;/p&gt;

&lt;h3&gt;
  
  
  plan-eng-review
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;/plan-eng-review&lt;/code&gt; shifts the perspective toward engineering: architecture, data flow, diagrams, edge cases, a test matrix, failure modes, and security concerns. It stays separate from the product review because merging both into one large prompt makes the model mix product decisions with implementation decisions.&lt;/p&gt;

&lt;h3&gt;
  
  
  plan-design-review and design-review
&lt;/h3&gt;

&lt;p&gt;gstack treats visual and interaction design as a separate discipline. &lt;code&gt;/plan-design-review&lt;/code&gt; rates each design dimension from 0 to 10, describes what a 10 looks like, and edits the plan to close the gap, with "AI slop" detection as a named check. The later &lt;code&gt;/design-review&lt;/code&gt; runs the same audit against the actual implementation and fixes what it finds with atomic commits and before/after screenshots. For web applications, both pair with gstack's browser automation.&lt;/p&gt;

&lt;h3&gt;
  
  
  review
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;/review&lt;/code&gt; performs engineering review against repository changes from a staff-engineer perspective: it auto-fixes the obvious findings, flags the rest for approval, and keeps an advisory simplification lens for over-built code. Code written successfully is not necessarily code that should be merged.&lt;/p&gt;

&lt;h3&gt;
  
  
  investigate
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;/investigate&lt;/code&gt; enforces a systematic debugging rule the project calls the Iron Law: no fixes without investigation. It traces data flow, tests hypotheses, and stops after three failed fix attempts instead of continuing to thrash. It also auto-activates &lt;code&gt;/freeze&lt;/code&gt;, which locks edits to the module under investigation.&lt;/p&gt;

&lt;h3&gt;
  
  
  qa and qa-only
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;/qa&lt;/code&gt; has the agent operate a browser, interact with the application, find bugs, fix them with atomic commits, re-verify, and generate a regression test for every fix. &lt;code&gt;/qa-only&lt;/code&gt; runs the same methodology report-only. Many coding agents stop verification at "tests passed"; for a web application the browser is where integration errors, layout problems, incorrect flows, authentication failures, and JavaScript exceptions usually become visible.&lt;/p&gt;

&lt;h3&gt;
  
  
  ship, land-and-deploy, and canary
&lt;/h3&gt;

&lt;p&gt;The release chain is three skills rather than one. &lt;code&gt;/ship&lt;/code&gt; syncs main, runs tests, audits coverage, pushes, and opens the pull request, bootstrapping a test framework if the project has none. &lt;code&gt;/land-and-deploy&lt;/code&gt; merges, waits for CI and the deployment, and verifies production health. &lt;code&gt;/canary&lt;/code&gt; then runs a post-deploy monitoring loop that watches for console errors, performance regressions, and page failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  autoplan, spec, learn, and retro
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;/autoplan&lt;/code&gt; runs the CEO, design, DX, and engineering review pipeline automatically -- engineering always last, so the shipping gate reviews the final amended plan -- and surfaces only taste decisions for approval. &lt;code&gt;/spec&lt;/code&gt; turns vague intent into a precise, executable spec in five phases (why, scope, technical with mandatory code-reading, draft, file) with an outside-review quality gate before filing. &lt;code&gt;/learn&lt;/code&gt; manages what gstack has learned across sessions -- patterns, pitfalls, and preferences -- with review, search, prune, and export. &lt;code&gt;/retro&lt;/code&gt; produces a team-aware weekly retro; &lt;code&gt;/retro global&lt;/code&gt; runs it across all your projects and AI tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Browser Automation in gstack
&lt;/h2&gt;

&lt;p&gt;On supported macOS systems (macOS 15+), gstack drives the &lt;a href="https://aside.com" rel="noopener noreferrer"&gt;Aside&lt;/a&gt; browser first -- your real browser, with your real logged-in sessions, in tabs the agent opens for itself and closes when done. When Aside is unavailable, gstack falls back to its own Chromium-based engine, which &lt;code&gt;./setup&lt;/code&gt; builds and which runs a persistent daemon rather than launching a fresh browser for every command:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A[Coding Agent] --&amp;gt; B[gstack Browser CLI]
    B --&amp;gt; C[Local Browser Service]
    C --&amp;gt; D[Aside or bundled Chromium]
    D --&amp;gt; E[Application]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Persistent browser state lets cookies, authentication sessions, and tabs survive between operations, which makes browser-based QA practical. &lt;code&gt;/open-gstack-browser&lt;/code&gt; exposes the fallback engine headed, with a sidebar agent that routes fast actions (click, navigate, screenshot) to Sonnet and reading or analysis to Opus. When the agent hits a CAPTCHA, an auth wall, or an MFA prompt, &lt;code&gt;$B handoff&lt;/code&gt; opens a visible browser at the same page with cookies and tabs intact; you solve it, and &lt;code&gt;$B resume&lt;/code&gt; continues where the agent left off. The agent suggests a handoff automatically after three consecutive failures. &lt;code&gt;/pair-agent&lt;/code&gt; shares the browser with other agents -- OpenClaw, Hermes, Codex, Cursor, or anything that can curl -- with scoped tokens, tab isolation, rate limiting, and per-tab activity attribution.&lt;/p&gt;

&lt;p&gt;The persistent engine also increases the security surface, since an agent with access to authenticated sessions holds a meaningful privilege. gstack ships a layered prompt-injection defense for this: content filters (datamarking, hidden-element stripping, ARIA scrubbing, URL blocklist) on every page read, plus a local ML classifier in a sidecar subprocess that scans page-derived content before the agent sees it, with a verdict combiner that requires classifier agreement before blocking. Page content is treated as untrusted input -- the agent takes syntax from a page, never instructions. Checks before and while using browser-driven QA:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Decide in advance which authenticated environments the agent may operate in, and prefer an isolated profile for QA work when the fallback engine is in use.&lt;/li&gt;
&lt;li&gt;Know the emergency kill switch: &lt;code&gt;GSTACK_SECURITY_OFF=1&lt;/code&gt; disables the security layer -- do not leave it set.&lt;/li&gt;
&lt;li&gt;The persistent daemon retains cookies and sessions between runs, so stop it when you are done and confirm nothing is left running, for example &lt;code&gt;ps aux | grep -i chrom&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Read the hooks and safety mechanisms in the cloned repository before enabling them -- they are plain files, so review them the way you would review CI configuration.&lt;/li&gt;
&lt;li&gt;After updating the clone, re-run &lt;code&gt;./setup&lt;/code&gt; so generated components stay in sync with the skill definitions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Safety Guardrails and Second Opinions
&lt;/h2&gt;

&lt;p&gt;Three power tools act as session-level safety switches. &lt;code&gt;/careful&lt;/code&gt; warns before destructive commands -- &lt;code&gt;rm -rf&lt;/code&gt;, &lt;code&gt;DROP TABLE&lt;/code&gt;, force-push, &lt;code&gt;git reset --hard&lt;/code&gt; -- and activates by saying "be careful"; recursive deletes of the root or home directory and force-pushes to the default branch are hard-denied. &lt;code&gt;/freeze&lt;/code&gt; restricts file edits to one directory so the agent cannot "fix" unrelated code while debugging, and &lt;code&gt;/guard&lt;/code&gt; activates both at once.&lt;/p&gt;

&lt;p&gt;Second-opinion reviews cross harnesses: on Claude Code, &lt;code&gt;/codex&lt;/code&gt; sends the work to OpenAI Codex CLI for an independent review, challenge, or consultation; on the other harnesses, &lt;code&gt;/claude-code&lt;/code&gt; does the reverse. Each report identifies the provider that actually completed the review.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical gstack Workflow
&lt;/h2&gt;

&lt;p&gt;You do not need every gstack skill for every change. A reasonable feature workflow:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    A[Feature idea] --&amp;gt; B[office-hours]
    B --&amp;gt; C[plan-ceo-review]
    C --&amp;gt; D[Create implementation plan]
    D --&amp;gt; E[plan-eng-review]
    E --&amp;gt; F[Implement]
    F --&amp;gt; G[review]
    G --&amp;gt; H[qa]
    H --&amp;gt; I[ship]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Which review skills to add depends on who the software is for:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Building for&lt;/th&gt;
&lt;th&gt;Plan stage (before code)&lt;/th&gt;
&lt;th&gt;Live audit (after shipping)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;End users (UI, web app, mobile)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/plan-design-review&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/design-review&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Developers (API, CLI, SDK, docs)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/plan-devex-review&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/devex-review&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Architecture (data flow, perf)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/plan-eng-review&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/review&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;All of the above&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/autoplan&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For a trivial bug fix, going straight to investigate, implementation, review, and tests is often enough. A tool-neutral version of the same shape -- spec, design, tasks, implement, validate -- is in &lt;a href="https://www.glukhov.org/app-architecture/documentation/spec-driven-development-workflow/" rel="noopener noreferrer"&gt;Spec-Driven Development Workflow From Requirements to Code&lt;/a&gt;. The project's README describes running ten to fifteen of these sprints in parallel, each in its own isolated workspace; the sprint structure is what the project says keeps parallel agents from becoming sources of chaos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing gstack
&lt;/h2&gt;

&lt;p&gt;The current installation expects a working Claude Code setup, Git, Bun v1.0+, and, on Windows, Node.js -- Bun has a known bug with Playwright's pipe transport on Windows, so the browse server falls back to Node.js there. If you have not configured Claude Code yet, start with the &lt;a href="https://www.glukhov.org/ai-devtools/claude-code/" rel="noopener noreferrer"&gt;Claude Code overview&lt;/a&gt; first. On macOS, the Aside browser (macOS 15+) is recommended for the browser skills; without it, the bundled Chromium daemon is used.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Verify your prerequisites: &lt;code&gt;git --version&lt;/code&gt; and &lt;code&gt;bun --version&lt;/code&gt; (the toolchain is Bun-based).&lt;/li&gt;
&lt;li&gt;Clone gstack into the Claude skills directory:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   git clone &lt;span class="nt"&gt;--single-branch&lt;/span&gt; &lt;span class="nt"&gt;--depth&lt;/span&gt; 1 &lt;span class="se"&gt;\&lt;/span&gt;
     https://github.com/garrytan/gstack.git &lt;span class="se"&gt;\&lt;/span&gt;
     ~/.claude/skills/gstack
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Run the setup script from the cloned directory:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;cd&lt;/span&gt; ~/.claude/skills/gstack
   ./setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setup installs and generates the components required by the supported skills and builds the bundled browser; a Chromium install failure is best-effort, setup records the reason, finishes registering every skill, and prints which skills are affected.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add a &lt;code&gt;## gstack&lt;/code&gt; section to the project's &lt;code&gt;CLAUDE.md&lt;/code&gt;. The project's install instructions include this step, and it is what makes Claude Code route the skills: use &lt;code&gt;/browse&lt;/code&gt; from gstack for all web browsing, never use &lt;code&gt;mcp__claude-in-chrome__*&lt;/code&gt; tools, and list the available skills.&lt;/li&gt;
&lt;li&gt;Verify the install: check that the generated files are present in the cloned directory, start a Claude Code session, and run &lt;code&gt;/office-hours&lt;/code&gt; on a scratch project to confirm the skill is recognized.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Team mode
&lt;/h3&gt;

&lt;p&gt;For repositories, gstack offers a team-oriented setup where developers share one workflow instead of individually configured environments:&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="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/.claude/skills/gstack &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./setup &lt;span class="nt"&gt;--team&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  ~/.claude/skills/gstack/bin/gstack-team-init required &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  git add .claude/ CLAUDE.md &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"require gstack for AI-assisted work"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;required&lt;/code&gt; blocks AI-assisted work in the repo without gstack; swap it for &lt;code&gt;optional&lt;/code&gt; to nudge teammates instead of blocking them. No files are vendored into the repo: every Claude Code session starts with a fast auto-update check (throttled to once per hour, network-failure-safe, silent), which removes version drift across the team. Personal configuration improves one developer; repository-level configuration creates a shared engineering convention.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other harnesses, upgrades, and uninstall
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Other agents: &lt;code&gt;./setup --host codex&lt;/code&gt;, &lt;code&gt;--host opencode&lt;/code&gt;, &lt;code&gt;--host cursor&lt;/code&gt;, &lt;code&gt;--host factory&lt;/code&gt;, &lt;code&gt;--host kiro&lt;/code&gt;, &lt;code&gt;--host slate&lt;/code&gt;, &lt;code&gt;--host openclaw&lt;/code&gt;, and &lt;code&gt;--host hermes&lt;/code&gt; install the skills into each agent's own skills directory. The 2KB instruction-only digest at &lt;code&gt;agents-digest/gstack-AGENTS.md&lt;/code&gt; covers agents that only read rules files.&lt;/li&gt;
&lt;li&gt;Command naming: skills register with short names by default (&lt;code&gt;/qa&lt;/code&gt;, &lt;code&gt;/review&lt;/code&gt;); &lt;code&gt;./setup --prefix&lt;/code&gt; switches to namespaced names (&lt;code&gt;/gstack-qa&lt;/code&gt;), which matters when you run other skill packs alongside gstack.&lt;/li&gt;
&lt;li&gt;Upgrades: re-run &lt;code&gt;./setup&lt;/code&gt; after a &lt;code&gt;git pull&lt;/code&gt; (required on Windows, where installs are file copies), or use the &lt;code&gt;/gstack-upgrade&lt;/code&gt; skill; setting &lt;code&gt;auto_upgrade: true&lt;/code&gt; in &lt;code&gt;~/.gstack/config.yaml&lt;/code&gt; keeps the install current automatically.&lt;/li&gt;
&lt;li&gt;Telemetry is off by default and asks for opt-in on first run. If you opt in, it sends skill name, duration, success/fail, gstack version, and OS -- never code, file paths, repo names, or prompts. &lt;code&gt;gstack-config set telemetry off&lt;/code&gt; disables it at any time.&lt;/li&gt;
&lt;li&gt;Uninstall: &lt;code&gt;~/.claude/skills/gstack/bin/gstack-uninstall&lt;/code&gt; removes skills, symlinks, &lt;code&gt;~/.gstack/&lt;/code&gt; state, project-local state, browse daemons, and hook registrations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Verify the install and fix common failures
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;./setup&lt;/code&gt; fails&lt;/strong&gt; -- confirm Bun is on your PATH with &lt;code&gt;bun --version&lt;/code&gt;; the generated components are built by the Bun toolchain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skills not recognized by Claude Code&lt;/strong&gt; -- confirm the clone actually lives in &lt;code&gt;~/.claude/skills/gstack&lt;/code&gt;, that the project's &lt;code&gt;CLAUDE.md&lt;/code&gt; has a gstack section, and re-run &lt;code&gt;./setup&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/browse&lt;/code&gt; reports &lt;code&gt;NEED_ASIDE&lt;/code&gt; or &lt;code&gt;ASIDE_NOT_RUNNING&lt;/code&gt;&lt;/strong&gt; -- the probe is telling you it will use the fallback browser. That is normal on Linux and Windows; on macOS it means Aside is not open or signed in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The fallback browser fails&lt;/strong&gt; -- &lt;code&gt;cd ~/.claude/skills/gstack &amp;amp;&amp;amp; bun install &amp;amp;&amp;amp; bun run build&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stale install after an update&lt;/strong&gt; -- run &lt;code&gt;/gstack-upgrade&lt;/code&gt;, or set &lt;code&gt;auto_upgrade: true&lt;/code&gt; in &lt;code&gt;~/.gstack/config.yaml&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Trying gstack Without Adopting Everything
&lt;/h2&gt;

&lt;p&gt;Use gstack on a real but non-critical feature rather than migrating your development process. The project's quick start is the same trial, and it ends with "stop there":&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;/office-hours&lt;/code&gt; -- problem definition&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/plan-ceo-review&lt;/code&gt; -- product reasoning&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/review&lt;/code&gt; -- engineering verification, after implementation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/qa&lt;/code&gt; -- runtime verification, for web projects&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If those stages surface findings your normal Claude Code workflow misses, the rest of the system is worth exploring; if they mostly produce additional text without changing engineering decisions, adopting the entire stack probably will not help.&lt;/p&gt;

&lt;h2&gt;
  
  
  What gstack Does Well
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Separated engineering roles.&lt;/strong&gt; Instead of one giant "be a senior engineer" instruction, product strategy, architecture, UX, QA, security, and release engineering each get their own reasoning mode.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verification, not just generation.&lt;/strong&gt; Review, browser QA with regression-test generation, security audits, benchmarking, and the ship-deploy-canary chain are first-class workflows in gstack rather than optional afterthoughts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inspectable.&lt;/strong&gt; Much of the behavioral layer is plain Markdown files developers can read and modify, unlike the internal workflows of a proprietary autonomous agent. The repo also ships audit tooling for the stack itself: &lt;code&gt;gstack-context-bill&lt;/code&gt; reports what an installed skill tree costs in tokens, and &lt;code&gt;gstack-egress&lt;/code&gt; writes a hash-chained receipt for every off-machine send, telemetry included.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Team infrastructure.&lt;/strong&gt; Skills can encode engineering conventions -- instead of typing&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Remember to check API compatibility, run integration tests,
inspect the browser console, and update the changelog.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;in every session, the requirements live in a reusable workflow, and team mode makes that workflow a repository requirement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where gstack Can Be Too Much
&lt;/h2&gt;

&lt;p&gt;gstack is intentionally opinionated, and that limits its fit: a mature organization may already have architecture review procedures, release tooling, CI gates, QA automation, security scanning, ADR conventions, specification templates, and code review policies, and adding another complete methodology on top creates overlap instead of clarity.&lt;/p&gt;

&lt;p&gt;There is also a context and token cost: each additional review stage adds repository inspection, model reasoning, and potentially more external model calls. The goal is the minimum reliable process needed to ship correct software, not a maximum number of AI reviews; &lt;code&gt;gstack-context-bill&lt;/code&gt; can quantify what your installed skill set actually costs per session before you decide how much of it to keep.&lt;/p&gt;

&lt;p&gt;gstack works best as a toolbox whose workflows you select and adapt, not as ceremony for every commit.&lt;/p&gt;

&lt;h2&gt;
  
  
  gstack Alternatives and Combinations
&lt;/h2&gt;

&lt;p&gt;The closest alternatives, and the layer each occupies:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;System&lt;/th&gt;
&lt;th&gt;Primary focus&lt;/th&gt;
&lt;th&gt;Workflow style&lt;/th&gt;
&lt;th&gt;Agent portability&lt;/th&gt;
&lt;th&gt;Best fit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;gstack&lt;/td&gt;
&lt;td&gt;Full engineering workflow&lt;/td&gt;
&lt;td&gt;Role-oriented skills and tools&lt;/td&gt;
&lt;td&gt;10 agents via &lt;code&gt;./setup --host&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;End-to-end AI-assisted engineering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Superpowers&lt;/td&gt;
&lt;td&gt;Engineering methodology&lt;/td&gt;
&lt;td&gt;Automatic composable skills&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Disciplined coding and TDD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenSpec&lt;/td&gt;
&lt;td&gt;Change specifications&lt;/td&gt;
&lt;td&gt;Lightweight spec artifacts&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Brownfield feature development&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Spec Kit&lt;/td&gt;
&lt;td&gt;Spec-driven development&lt;/td&gt;
&lt;td&gt;Structured multi-stage workflow&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Formal requirements-to-code process&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BMAD Method&lt;/td&gt;
&lt;td&gt;AI-driven agile development&lt;/td&gt;
&lt;td&gt;Adaptive roles and workflows&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Larger end-to-end projects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ruflo&lt;/td&gt;
&lt;td&gt;Multi-agent orchestration&lt;/td&gt;
&lt;td&gt;Agents, swarms, memory&lt;/td&gt;
&lt;td&gt;Platform-oriented&lt;/td&gt;
&lt;td&gt;Parallel autonomous agent systems&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom skills&lt;/td&gt;
&lt;td&gt;Your own process&lt;/td&gt;
&lt;td&gt;Fully customizable&lt;/td&gt;
&lt;td&gt;Potentially very high&lt;/td&gt;
&lt;td&gt;Mature teams with established practices&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  gstack + Superpowers: implementation discipline inside the roles
&lt;/h3&gt;

&lt;p&gt;Both are skill frameworks, so they overlap the most. The division of labor when combining them: gstack supplies the surrounding roles -- product, design, QA, release -- while Superpowers supplies the discipline inside the implementation phase (TDD, planning before implementation, systematic debugging, subagent review). Install both skill sets, then trim the overlapping skills so the agent never sees two conflicting instructions for the same phase; if command names collide, install gstack with &lt;code&gt;./setup --prefix&lt;/code&gt; so its skills register as &lt;code&gt;/gstack-*&lt;/code&gt; and coexist with the other pack. Install and workflow details are in the &lt;a href="https://www.glukhov.org/ai-devtools/superpowers/" rel="noopener noreferrer"&gt;Superpowers quickstart&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  gstack + OpenSpec: durable specs, live reviews
&lt;/h3&gt;

&lt;p&gt;OpenSpec keeps human and agent aligned around explicit change specifications -- artifacts for the proposed change, specifications, design decisions, and implementation tasks. The key property is persistence: a chat conversation disappears into context history, but a specification remains in the repository where humans and future agent sessions can review it. gstack adds the product review before the spec exists and the review and QA after it is implemented:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A[Feature request] --&amp;gt; B[gstack product review]
    B --&amp;gt; C[OpenSpec change]
    C --&amp;gt; D[Implementation]
    D --&amp;gt; E[gstack review]
    E --&amp;gt; F[gstack QA]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;A concrete sequence: run &lt;code&gt;/office-hours&lt;/code&gt; and &lt;code&gt;/plan-ceo-review&lt;/code&gt;, capture the outcome as an OpenSpec change, implement against it, then run &lt;code&gt;/review&lt;/code&gt; and &lt;code&gt;/qa&lt;/code&gt;. Note that gstack also ships its own &lt;code&gt;/spec&lt;/code&gt; skill, which archives specs under &lt;code&gt;~/.gstack&lt;/code&gt;; if OpenSpec owns the specification, keep gstack's &lt;code&gt;/spec&lt;/code&gt; out of the loop so the two do not diverge. The &lt;a href="https://www.glukhov.org/ai-devtools/openspec/" rel="noopener noreferrer"&gt;OpenSpec quickstart&lt;/a&gt; covers the explore-propose-apply-archive loop in detail.&lt;/p&gt;

&lt;h3&gt;
  
  
  gstack + GitHub Spec Kit: pick one planning backbone
&lt;/h3&gt;

&lt;p&gt;Spec Kit's core workflow is a sequence of explicit stages -- constitution, specify, plan, tasks, implement, converge -- and it has expanded into bug-fixing, idea-assessment, extensions, presets, and integrations. Since both Spec Kit and gstack center the planning stage, running both full flows duplicates work. If requirements traceability and formal stages matter, let Spec Kit own the specification backbone and use gstack for the layers Spec Kit does not enforce -- product review, design review, browser QA, and shipping. A broader comparison of spec-driven setups, including Kiro and Claude Code, is in &lt;a href="https://www.glukhov.org/ai-devtools/ai-coding-assistants/spec-kit-vs-kiro-vs-claude-code/" rel="noopener noreferrer"&gt;GitHub Spec Kit vs Kiro vs Claude Code SDD Workflows&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  BMAD and Ruflo: different axes
&lt;/h3&gt;

&lt;p&gt;BMAD is a broader AI-driven development methodology whose adaptive workflows cover product thinking, specifications, architecture, and implementation, scaling the ceremony to the size of the work. It and gstack both play the process-backbone role, so choose one as the backbone rather than running both in full; gstack's individual skills can still be selected alongside a methodology.&lt;/p&gt;

&lt;p&gt;Ruflo targets multi-agent orchestration: coordinated workers, shared memory, swarms. gstack applies multiple specialist perspectives to one engineering workflow; an orchestration platform applies multiple executing agents to one engineering objective. The boundary blurs -- gstack can call external tools and additional models, and orchestrators can implement structured engineering roles -- but the decision is independent: if the problem is that the agent skips engineering discipline, a skill framework is the direct fix; if it is running ten agents concurrently across many tasks and repositories, an orchestrator sits above a workflow like gstack rather than replacing it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom skills: the most customizable layer
&lt;/h3&gt;

&lt;p&gt;You can also skip the framework entirely and create a small collection of skills for the procedures your team already follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;skills/
  architecture-review/
  api-review/
  database-migration-review/
  incident-analysis/
  release-check/
  security-review/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each skill encodes organization-specific knowledge a generic framework cannot know. A database migration skill can require rollback analysis, table-lock analysis, index impact review, migration duration estimation, deployment ordering, and compatibility with the previous application version; an API review skill can require backwards compatibility, authentication checks, pagination consistency, idempotency analysis, rate-limit behavior, and OpenAPI changes. A practical path: start from the gstack skills you actually use, copy their structure into your own &lt;code&gt;skills/&lt;/code&gt; directory, and rewrite the checks around your conventions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four Layers: Skills, Specifications, Methodologies, Orchestrators
&lt;/h2&gt;

&lt;p&gt;Four layers cover most of these tools, and they show how the combinations above fit together:&lt;/p&gt;

&lt;h3&gt;
  
  
  Skills answer "how should the agent behave?"
&lt;/h3&gt;

&lt;p&gt;gstack, Superpowers, and custom agent skills.&lt;/p&gt;

&lt;h3&gt;
  
  
  Specification systems answer "what exactly are we building?"
&lt;/h3&gt;

&lt;p&gt;OpenSpec and GitHub Spec Kit; the underlying spec-driven concepts and terminology are defined in &lt;a href="https://www.glukhov.org/app-architecture/documentation/what-is-spec-driven-development/" rel="noopener noreferrer"&gt;What Is Spec-Driven Development?&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Methodologies answer "how should the project move from idea to software?"
&lt;/h3&gt;

&lt;p&gt;BMAD, Superpowers, and parts of gstack.&lt;/p&gt;

&lt;h3&gt;
  
  
  Orchestrators answer "how should multiple agents execute work?"
&lt;/h3&gt;

&lt;p&gt;Ruflo and other multi-agent runtimes.&lt;/p&gt;

&lt;p&gt;The layers compose; a development environment can contain all four:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    A[Product requirement] --&amp;gt; B[Specification system]
    B --&amp;gt; C[Engineering workflow]
    C --&amp;gt; D[Agent orchestrator]

    D --&amp;gt; E[Implementation agent]
    D --&amp;gt; F[Test agent]
    D --&amp;gt; G[Review agent]
    D --&amp;gt; H[QA agent]

    E --&amp;gt; I[Repository]
    F --&amp;gt; I
    G --&amp;gt; I
    H --&amp;gt; I&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;gstack already spans several of these boundaries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should You Use gstack?
&lt;/h2&gt;

&lt;p&gt;The project's README describes the audience as technical founders and CEOs who still want to ship, first-time Claude Code users who want structured roles instead of a blank prompt, and tech leads and staff engineers who want rigorous review, QA, and release automation on every PR. gstack is worth trying if you use coding agents extensively and the limiting factor is no longer code generation itself. Typical symptoms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the agent starts implementing before understanding the problem&lt;/li&gt;
&lt;li&gt;implementation plans miss architectural implications&lt;/li&gt;
&lt;li&gt;generated code passes tests but fails in the browser&lt;/li&gt;
&lt;li&gt;reviews are inconsistent between sessions&lt;/li&gt;
&lt;li&gt;release steps are repeatedly forgotten&lt;/li&gt;
&lt;li&gt;different developers prompt the agent in completely different ways&lt;/li&gt;
&lt;li&gt;useful engineering instructions remain buried in CLAUDE.md files&lt;/li&gt;
&lt;li&gt;you repeatedly type the same review prompts manually&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your automation already provides strong deterministic gates and the agent only handles small, well-specified tasks, gstack adds little.&lt;/p&gt;

&lt;h2&gt;
  
  
  gstack and the Direction of AI Software Development
&lt;/h2&gt;

&lt;p&gt;The shift gstack sits in tracks in generations: code completion (2022-2023), coding agents (2024-2025), specifications and agent workflows (2025-2026), and programmable AI engineering organizations. The products will change, but the model remains one component; engineering quality increasingly depends on the surrounding system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;persistent specifications&lt;/li&gt;
&lt;li&gt;reusable skills&lt;/li&gt;
&lt;li&gt;repository knowledge&lt;/li&gt;
&lt;li&gt;browser access&lt;/li&gt;
&lt;li&gt;tests&lt;/li&gt;
&lt;li&gt;deterministic tools&lt;/li&gt;
&lt;li&gt;review loops&lt;/li&gt;
&lt;li&gt;security controls&lt;/li&gt;
&lt;li&gt;memory&lt;/li&gt;
&lt;li&gt;human approval boundaries&lt;/li&gt;
&lt;li&gt;orchestration&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;gstack is the engineering process around a coding agent, packaged as inspectable, version-controlled skills, and its value is in forcing that process onto the agent rather than in any single skill.&lt;/p&gt;

&lt;p&gt;Start from the trial sequence and keep only the skills that earn their place. Beyond it, the direction is composable layers -- specification, skills, deterministic verification, orchestration -- each doing what the others cannot.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/garrytan/gstack" rel="noopener noreferrer"&gt;gstack repository&lt;/a&gt; -- source, skill definitions, and setup&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/garrytan/gstack/blob/main/docs/skills.md" rel="noopener noreferrer"&gt;gstack skill deep dives&lt;/a&gt; -- philosophy, examples, and workflow for every skill&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aside.com" rel="noopener noreferrer"&gt;Aside browser&lt;/a&gt; -- the browser gstack drives first on macOS&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/obra/superpowers" rel="noopener noreferrer"&gt;Superpowers repository&lt;/a&gt; -- source, skills, and plugin manifests&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Fission-AI/OpenSpec" rel="noopener noreferrer"&gt;OpenSpec repository&lt;/a&gt; -- source, docs, and the CLI package&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.github.io/spec-kit/" rel="noopener noreferrer"&gt;GitHub Spec Kit documentation&lt;/a&gt; -- official Spec Kit workflow reference&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/bmad-code-org/BMAD-METHOD" rel="noopener noreferrer"&gt;BMAD-METHOD repository&lt;/a&gt; -- the adaptive AI-driven agile methodology&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ruvnet/ruflo" rel="noopener noreferrer"&gt;Ruflo repository&lt;/a&gt; -- the multi-agent orchestration platform&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aicoding</category>
      <category>aiagents</category>
      <category>opensource</category>
      <category>llm</category>
    </item>
    <item>
      <title>Self-Hosted Deep Research Systems: 12 Tools Compared</title>
      <dc:creator>Rost</dc:creator>
      <pubDate>Fri, 25 Sep 2026 23:30:04 +0000</pubDate>
      <link>https://dev.to/rosgluk/self-hosted-deep-research-systems-12-tools-compared-7pp</link>
      <guid>https://dev.to/rosgluk/self-hosted-deep-research-systems-12-tools-compared-7pp</guid>
      <description>&lt;p&gt;Deep Research has become its own category of software, not just a model pointed at a search box. This article compares twelve self-hosted systems and the research architectures behind them.&lt;/p&gt;

&lt;p&gt;The line that actually matters is not whether a product ships a button labelled Deep Research, but what happens after the first round of retrieval. A genuine system notices that its plan was incomplete, chases a newly discovered lead, weighs conflicting sources, and only then writes the report.&lt;/p&gt;

&lt;p&gt;Below I compare twelve open-source and self-hosted projects that implement this loop in different ways: recursive research trees, planner-plus-subagent designs, evidence-gap loops, perspective-driven question generation, and model-driven agentic search. For each one I cover the architecture, local-LLM support, RAG or private-document access, deployment complexity, and the license you actually inherit if you self-host. Where a system is also a full product (Open WebUI, Vane), I keep the focus on how it researches and link out to the dedicated guide for installation and configuration. Deep Research is one of the more demanding applied workloads in the &lt;a href="https://www.glukhov.org/ai-systems/" rel="noopener noreferrer"&gt;AI Systems&lt;/a&gt; - it stresses retrieval, planning, and multi-step orchestration all at once, rather than any single layer in isolation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Deep Research?
&lt;/h2&gt;

&lt;p&gt;A conventional AI web-search workflow is mostly linear. Even when several searches are performed, the model usually just creates related queries, retrieves documents, and summarizes what it finds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Question
  |
Search
  |
Retrieve pages
  |
Summarize
  |
Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deep Research adds a layer: the research process itself becomes adaptive. After the first pass, the system can branch, re-check, and keep going until the evidence is sufficient.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    A[Research question] --&amp;gt; B[Build research plan]
    B --&amp;gt; C1[Investigate topic A]
    B --&amp;gt; C2[Investigate topic B]
    B --&amp;gt; C3[Investigate topic C]

    C1 --&amp;gt; D1[Discover new question]
    C2 --&amp;gt; D2[Find conflicting evidence]
    C3 --&amp;gt; D3[Identify missing information]

    D1 --&amp;gt; E1[Research new question]
    D2 --&amp;gt; E2[Verify competing claims]
    D3 --&amp;gt; E3[Search for missing evidence]

    E1 --&amp;gt; F[Combine evidence]
    E2 --&amp;gt; F
    E3 --&amp;gt; F

    F --&amp;gt; G[Evaluate remaining gaps]
    G --&amp;gt;|More research needed| B
    G --&amp;gt;|Enough evidence| H[Generate cited report]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The distinction matters. A system that searches five times is not necessarily performing Deep Research; a stronger system starts with one question, discovers an unexpected implementation detail, opens a new research branch around it, compares primary and secondary sources, and revises its original assumptions. For the broader Search vs Deep Search vs Deep Research distinction and how cloud offerings frame the same idea, see &lt;a href="https://www.glukhov.org/rag/architecture/search-vs-deepsearch-vs-deep-research/" rel="noopener noreferrer"&gt;Search vs Deep Search vs Deep Research in 2026&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There is no single Deep Research architecture. Current self-hosted implementations generally fall into five groups:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Recursive research trees.&lt;/li&gt;
&lt;li&gt;Planner and subagent architectures.&lt;/li&gt;
&lt;li&gt;Evidence-gap-driven research loops.&lt;/li&gt;
&lt;li&gt;Perspective and question-driven research.&lt;/li&gt;
&lt;li&gt;Agentic iterative search.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first four provide more explicit research structure. The fifth can still perform surprisingly deep investigation when paired with a strong reasoning and tool-calling model, but much of the strategy is delegated to the model itself. The evidence-gap loop in particular is a system-level cousin of the self-reflective retrieval used in Self-RAG-style pipelines — deciding whether to retrieve again, judging relevance, and critiquing the draft before answering. See &lt;a href="https://www.glukhov.org/rag/architecture/advanced-rag-variants-longrag-self-rag-graphrag/" rel="noopener noreferrer"&gt;Advanced RAG: LongRAG, Self-RAG and GraphRAG&lt;/a&gt; for that pattern at the retrieval-pipeline level.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-Hosted Deep Research Systems Compared
&lt;/h2&gt;

&lt;p&gt;The table below summarizes the major systems. "Recursive depth" does not mean that multiple web searches are possible; it means the system has some mechanism for deriving additional investigation from intermediate findings.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;System&lt;/th&gt;
&lt;th&gt;Local LLM&lt;/th&gt;
&lt;th&gt;Web Research&lt;/th&gt;
&lt;th&gt;Private Docs / RAG&lt;/th&gt;
&lt;th&gt;Planning&lt;/th&gt;
&lt;th&gt;Recursive / Adaptive Depth&lt;/th&gt;
&lt;th&gt;UI&lt;/th&gt;
&lt;th&gt;Research Style&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GPT Researcher&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Web UI&lt;/td&gt;
&lt;td&gt;Recursive breadth/depth research tree&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unsloth Studio&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Very good&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Planned evidence-driven research&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local Deep Research&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Web UI&lt;/td&gt;
&lt;td&gt;Multiple strategies plus autonomous agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;STORM / Co-STORM&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Custom corpus possible&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Very good&lt;/td&gt;
&lt;td&gt;Basic / demo UI&lt;/td&gt;
&lt;td&gt;Perspective and follow-up-question research&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeerFlow&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Planner plus subagents and long-horizon agents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Onyx&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Multi-step enterprise Deep Research&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open Deep Research&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Via tools / MCP&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;LangGraph oriented&lt;/td&gt;
&lt;td&gt;Planner plus parallel researchers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open WebUI&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Model driven&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Agentic iterative search and link following&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Khoj&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Personal knowledge plus autonomous research&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SurfSense&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Web/data research plus knowledge workspace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vane&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;File search&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Search-first answering engine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deep Research by lukeswade&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Research library&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Web UI&lt;/td&gt;
&lt;td&gt;Gap-driven iterative investigation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One point stands out: there is no direct relationship between UI sophistication and research depth. Open WebUI and Vane provide polished interfaces, while GPT Researcher and STORM are centered more on the research algorithm. Conversely, Onyx and Unsloth Studio try to provide both a strong user experience and a substantial research workflow. Most of these systems run against the same local inference backends covered in the &lt;a href="https://www.glukhov.org/llm-hosting/" rel="noopener noreferrer"&gt;LLM Hosting guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deep Research Architectures
&lt;/h2&gt;

&lt;p&gt;Before comparing individual products, it is useful to understand the architectural differences.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Style&lt;/th&gt;
&lt;th&gt;Representative Systems&lt;/th&gt;
&lt;th&gt;Main Idea&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Recursive research tree&lt;/td&gt;
&lt;td&gt;GPT Researcher&lt;/td&gt;
&lt;td&gt;Explicit breadth and depth generate new research branches&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Planner plus subagents&lt;/td&gt;
&lt;td&gt;DeerFlow, Open Deep Research&lt;/td&gt;
&lt;td&gt;Planner decomposes work and independent agents investigate pieces&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Evidence-gap driven&lt;/td&gt;
&lt;td&gt;Unsloth Studio, Local Deep Research, lukeswade/deep-research&lt;/td&gt;
&lt;td&gt;Findings are evaluated and missing evidence triggers another research round&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Perspective driven&lt;/td&gt;
&lt;td&gt;STORM / Co-STORM&lt;/td&gt;
&lt;td&gt;Research is expanded by generating perspectives and follow-up questions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-step research workflow&lt;/td&gt;
&lt;td&gt;Onyx&lt;/td&gt;
&lt;td&gt;Multiple research tasks gather and synthesize web and private knowledge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agentic iterative search&lt;/td&gt;
&lt;td&gt;Open WebUI&lt;/td&gt;
&lt;td&gt;Model decides when to search, read, verify, and search again&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Knowledge-first research&lt;/td&gt;
&lt;td&gt;Khoj, SurfSense&lt;/td&gt;
&lt;td&gt;Research combines private information with external sources&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search-first answering&lt;/td&gt;
&lt;td&gt;Vane&lt;/td&gt;
&lt;td&gt;Search and retrieval are optimized primarily for cited answers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The categories overlap. Local Deep Research offers several research strategies, and DeerFlow 2.0 is a general-purpose agent platform that can perform research rather than a research-only application. The distinction is nevertheless useful when choosing a system: a recursively branching researcher behaves differently from a chat interface whose model simply has a &lt;code&gt;search_web&lt;/code&gt; tool. Systems that retrieve private documents alongside the web lean on the same retrieval patterns described in the &lt;a href="https://www.glukhov.org/rag/" rel="noopener noreferrer"&gt;RAG cluster&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  License Comparison
&lt;/h2&gt;

&lt;p&gt;Licensing is particularly important if the system will become part of an internal platform, commercial service, or redistributed product.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;System&lt;/th&gt;
&lt;th&gt;License&lt;/th&gt;
&lt;th&gt;Licensing Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GPT Researcher&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;Current &lt;code&gt;pyproject.toml&lt;/code&gt; declares MIT; some older package metadata still reports Apache-2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unsloth Studio&lt;/td&gt;
&lt;td&gt;AGPL-3.0&lt;/td&gt;
&lt;td&gt;Studio UI is AGPL-3.0; core Unsloth remains Apache-2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local Deep Research&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;Permissive open-source license&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;STORM / Co-STORM&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;Permissive open-source license&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeerFlow&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;Applies to current DeerFlow 2.0 repository&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Onyx&lt;/td&gt;
&lt;td&gt;MIT plus Enterprise License&lt;/td&gt;
&lt;td&gt;Core is MIT; &lt;code&gt;ee&lt;/code&gt; directories use the Onyx Enterprise License; &lt;code&gt;onyx-foss&lt;/code&gt; is 100 percent MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open Deep Research&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;Repository was archived in August 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open WebUI&lt;/td&gt;
&lt;td&gt;Open WebUI License&lt;/td&gt;
&lt;td&gt;Current versions include branding restrictions; older code has MIT/BSD history&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Khoj&lt;/td&gt;
&lt;td&gt;AGPL-3.0-or-later&lt;/td&gt;
&lt;td&gt;Network copyleft should be considered for modified hosted deployments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SurfSense&lt;/td&gt;
&lt;td&gt;Apache-2.0&lt;/td&gt;
&lt;td&gt;Current repository declares Apache-2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vane&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;Formerly known as Perplexica&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deep Research by lukeswade&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;td&gt;Permissive open-source license&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For private self-hosting, none of these licenses prevents normal use. The differences matter when modifying the software, offering it to other users, embedding it into another commercial application, or redistributing derivatives. MIT and Apache-2.0 are generally the simplest options for integration. AGPL-3.0 deserves closer review for network-accessible modified deployments, and Open WebUI's current license adds its own branding conditions.&lt;/p&gt;

&lt;h2&gt;
  
  
  GPT Researcher
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/assafelovic/gpt-researcher" rel="noopener noreferrer"&gt;GPT Researcher&lt;/a&gt; is developed by Assaf Elovic and contributors as an autonomous research agent focused specifically on comprehensive online investigation. It is one of the clearest reference implementations of what "Deep Research" means when the term describes an algorithm rather than a user-interface feature.&lt;/p&gt;

&lt;p&gt;Its strongest feature is explicit breadth and depth. Deep Research mode exposes parameters such as &lt;code&gt;deep_research_breadth&lt;/code&gt;, &lt;code&gt;deep_research_depth&lt;/code&gt;, and concurrency, allowing one investigation to generate several branches and those branches to generate additional research. This creates a real research tree rather than a fixed collection of search queries.&lt;/p&gt;

&lt;p&gt;That approach also has costs. Recursive expansion can produce many retrieval and LLM operations, and the quality of the final result depends heavily on the model's ability to formulate useful research questions, extract evidence, and avoid propagating weak assumptions into deeper levels. GPT Researcher is also more research-engine oriented than applications such as Open WebUI or Unsloth Studio.&lt;/p&gt;

&lt;p&gt;Installation is moderate rather than trivial: the project uses Python and ships a web application, while useful deployments also require suitable model and search providers. Current project metadata declares the MIT license. Choose GPT Researcher when explicit research depth, configurable recursion, and a research-first architecture matter more than an all-purpose local AI workstation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unsloth Studio
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/unslothai/unsloth" rel="noopener noreferrer"&gt;Unsloth Studio&lt;/a&gt; is developed by the Unsloth team as part of the broader Unsloth ecosystem. Originally best known for efficient model fine-tuning, Unsloth has expanded Studio into a local AI environment for inference, chat, tools, RAG, model management, and now Deep Research.&lt;/p&gt;

&lt;p&gt;The interesting aspect of Studio is how tightly research is integrated with local model operation. Its Deep Research workflow includes a planning stage, plan review, evidence gathering, report generation, document handling, and failure handling when research steps fail to gather evidence. For users already running GGUF or other local models, this makes Studio considerably more convenient than assembling a separate research framework, inference server, and frontend.&lt;/p&gt;

&lt;p&gt;Studio does not expose the same simple breadth/depth research-tree abstraction as GPT Researcher. Much of the workflow is organized around a research plan and evidence collection rather than arbitrary recursive expansion, and the feature is newer than some dedicated research projects. The quality of local research also remains sensitive to context length, output limits, tool use, and the reasoning quality of the selected model.&lt;/p&gt;

&lt;p&gt;Installation is relatively friendly because Unsloth now provides Studio and desktop-oriented workflows across major platforms, although GPU and model configuration can still become substantial for advanced local deployments. The Studio component is AGPL-3.0, while the core Unsloth package remains Apache-2.0. Choose Unsloth Studio when Deep Research should be part of a broader local-model workstation rather than a standalone research service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local Deep Research
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/LearningCircuit/local-deep-research" rel="noopener noreferrer"&gt;Local Deep Research&lt;/a&gt; is maintained by LearningCircuit and contributors as a privacy-oriented open-source research assistant. Its explicit goal is systematic research using web sources, academic databases, private documents, and local language models.&lt;/p&gt;

&lt;p&gt;Its major advantage is architectural flexibility. Rather than enforcing one research algorithm, Local Deep Research supports pipeline-oriented strategies as well as a LangGraph agent strategy in which the model can decide what to search, which specialist sources to use, and when enough evidence has been collected. Academic sources such as arXiv, PubMed, Semantic Scholar, and other search mechanisms make it particularly attractive for technical and scientific research.&lt;/p&gt;

&lt;p&gt;The downside of flexibility is complexity. Different strategies can behave substantially differently, which makes results harder to characterize with one simple "depth" parameter. It also has more moving pieces than a conventional chat UI, and users looking only for fast AI-assisted web answers may find it unnecessarily elaborate.&lt;/p&gt;

&lt;p&gt;The project supports local operation and has developed a substantial application around the underlying research engine. Its license is MIT. Choose Local Deep Research when privacy, local inference, multiple research strategies, academic information sources, and control over the research process are more important than a minimal setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  STORM and Co-STORM
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/stanford-oval/storm" rel="noopener noreferrer"&gt;STORM&lt;/a&gt;, developed by Stanford OVAL, stands for Synthesis of Topic Outlines through Retrieval and Multi-perspective Question Asking. It was designed around knowledge curation and long-form report generation rather than general AI chat.&lt;/p&gt;

&lt;p&gt;STORM's distinctive technique is perspective-driven research. It tries to discover different perspectives on a subject and uses question asking to broaden the information collected before writing an outline and article. Co-STORM extends the concept toward collaborative human-AI knowledge curation. This can uncover dimensions of a topic that a conventional list of keyword searches may overlook.&lt;/p&gt;

&lt;p&gt;STORM is less suitable as a general local AI frontend. Its workflow is strongly oriented toward researching and writing structured, Wikipedia-like articles, and the project's own documentation notes that generated output should not automatically be considered publication-ready. It is therefore better understood as a specialized research and knowledge-curation engine than a replacement for Open WebUI.&lt;/p&gt;

&lt;p&gt;Installation is Python oriented, and the pipeline can be customized with different models and retrievers. STORM uses the MIT license. Choose it when the objective is broad topic exploration, perspective discovery, structured outlines, and long-form knowledge synthesis.&lt;/p&gt;

&lt;h2&gt;
  
  
  DeerFlow
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/bytedance/deer-flow" rel="noopener noreferrer"&gt;DeerFlow&lt;/a&gt; is developed by ByteDance and the DeerFlow community. Its name originally stood for Deep Exploration and Efficient Research Flow, but an important distinction now exists between the original 1.x Deep Research framework and DeerFlow 2.0.&lt;/p&gt;

&lt;p&gt;DeerFlow 1.x was specifically designed around Deep Research. DeerFlow 2.0 is a ground-up rewrite into a more general SuperAgent harness capable of orchestrating subagents, memory, sandboxes, tools, and skills. For research, this architecture is powerful because a coordinator can delegate different parts of a problem to separate agents and later synthesize their findings - the same planner-plus-subagent decomposition covered in &lt;a href="https://www.glukhov.org/ai-systems/architecture/multi-agent-orchestration-patterns/" rel="noopener noreferrer"&gt;Multi-Agent Orchestration Patterns&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The trade-off is that DeerFlow 2.0 is no longer a narrowly optimized research engine. It is closer to a general long-horizon agent platform in which research is one workload among coding, artifact generation, and other tasks. If the requirement is a small dedicated research service, this additional machinery can be unnecessary.&lt;/p&gt;

&lt;p&gt;Deployment is consequently more involved than a simple search UI, although the architecture provides much more room for customization and extension. DeerFlow is MIT licensed. Choose DeerFlow when Deep Research is expected to become one capability inside a broader multi-agent automation environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Onyx
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/onyx-dot-app/onyx" rel="noopener noreferrer"&gt;Onyx&lt;/a&gt;, originally known as Danswer, is developed by DanswerAI and positioned as a self-hostable AI application layer for organizations. It combines chat, agents, web search, RAG, MCP integration, many enterprise data connectors, and a dedicated Deep Research capability.&lt;/p&gt;

&lt;p&gt;Onyx stands out because research can span both the public web and a substantial private knowledge environment. Its Deep Research implementation is a real multi-step research flow rather than merely a search-result summarizer, and the project has published results and execution logs for DeepResearch Bench. For organizations that need research across internal documentation, indexed applications, and external sources, this is a particularly strong combination.&lt;/p&gt;

&lt;p&gt;The cost of those capabilities is infrastructure complexity. Onyx is a larger platform than GPT Researcher or a lightweight local research project, and many of its strengths only matter when connectors, indexing, authentication, document stores, and organizational data are actually being used.&lt;/p&gt;

&lt;p&gt;Onyx can be deployed self-hosted, including in restricted environments. Most of the main repository is MIT licensed, while code under &lt;code&gt;ee&lt;/code&gt; directories uses the Onyx Enterprise License; a separate &lt;code&gt;onyx-foss&lt;/code&gt; repository is maintained as a fully MIT-licensed variant. Choose Onyx when Deep Research must coexist with serious enterprise RAG and organizational knowledge retrieval.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open Deep Research
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/langchain-ai/open_deep_research" rel="noopener noreferrer"&gt;Open Deep Research&lt;/a&gt; was developed by LangChain as an open implementation of a configurable Deep Research agent. It combines planning, research, report generation, multiple model providers, search tools, and MCP integrations using LangGraph.&lt;/p&gt;

&lt;p&gt;Its architecture is particularly interesting for developers. Research work can be decomposed and parallelized, making it a useful reference for planner-researcher-synthesizer designs. Because the project was built around LangGraph rather than a monolithic UI, it is also easier to study as an implementation pattern for building custom research agents.&lt;/p&gt;

&lt;p&gt;There is one major problem for new deployments: LangChain archived the repository on August 21, 2026, and it is now read-only. The code remains useful, but starting a production system around an archived reference project creates an obvious maintenance risk.&lt;/p&gt;

&lt;p&gt;The project is Python based and uses an MIT license. Choose it today mainly for architectural study, experimentation, or as a source of implementation ideas rather than as the default foundation for a new long-lived installation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open WebUI
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/open-webui/open-webui" rel="noopener noreferrer"&gt;Open WebUI&lt;/a&gt; is one of the most popular general-purpose self-hosted interfaces for local and remote LLMs. Its recent agentic tool architecture gives models access to web search, URL fetching, knowledge bases, files, memory, code execution, and other tools. For installation, RAG setup, and the full feature set, see the &lt;a href="https://www.glukhov.org/llm-hosting/llm-frontends/open-webui-overview-quickstart-and-alternatives/" rel="noopener noreferrer"&gt;Open WebUI guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Open WebUI's research model is interesting because the research loop is largely controlled by the language model. In native agentic mode the model can search, inspect snippets, fetch full pages, identify missing information, follow newly discovered URLs, cross-check sources, and repeat the process before generating an answer. With a capable reasoning and tool-calling model, this can produce genuine investigative behavior without a dedicated fixed research tree.&lt;/p&gt;

&lt;p&gt;The limitation is precisely that this structure is model driven. Open WebUI does not provide the same explicit breadth/depth research topology as GPT Researcher, and there is less deterministic control over how many independent branches will be explored. A weak tool-calling model can stop too early, search poorly, or fail to follow important leads.&lt;/p&gt;

&lt;p&gt;Installation is among the easiest in this comparison, particularly for users who already run Ollama, llama.cpp, vLLM, or another OpenAI-compatible inference server. Current releases use the Open WebUI License, which retains substantial permissive characteristics but adds branding restrictions; earlier portions of the project have MIT and BSD-3-Clause history. Choose Open WebUI when you want excellent local-LLM integration and a general AI interface in which research is one of many agentic capabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Khoj
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/khoj-ai/khoj" rel="noopener noreferrer"&gt;Khoj&lt;/a&gt; is developed as a self-hostable personal AI and "second brain." It combines local or cloud language models with web retrieval, personal documents, semantic search, custom agents, automations, and an experimental &lt;code&gt;/research&lt;/code&gt; mode.&lt;/p&gt;

&lt;p&gt;Its strongest use case is research that crosses the boundary between public information and a user's existing knowledge base. A question can be investigated in the context of PDFs, Markdown files, notes, office documents, or connected information rather than treating every task as web research from scratch. This makes Khoj useful for ongoing personal or team knowledge work.&lt;/p&gt;

&lt;p&gt;Khoj is not primarily designed around a visible recursive research tree. Its research functionality is better understood as autonomous investigation within a larger personal knowledge system. Users seeking explicit breadth/depth controls or a dedicated research-engine API may prefer GPT Researcher or Local Deep Research.&lt;/p&gt;

&lt;p&gt;Self-hosting is supported and the system can work with local models including the Llama, Qwen, Gemma, and Mistral families. Khoj is licensed under AGPL-3.0-or-later. Choose it when Deep Research should be closely integrated with a long-lived personal knowledge base rather than treated as an isolated web research job.&lt;/p&gt;

&lt;h2&gt;
  
  
  SurfSense
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/MODSetter/SurfSense" rel="noopener noreferrer"&gt;SurfSense&lt;/a&gt; is an open-source research workspace that evolved from a NotebookLM-style knowledge system toward an agent-oriented open-web research platform. It combines a searchable knowledge base with web and platform-specific data connectors, reports, automations, MCP access, and local model support.&lt;/p&gt;

&lt;p&gt;SurfSense's distinctive advantage is its data surface. It is designed to give agents structured access not only to ordinary web pages and search results but also to sources such as Reddit, YouTube, Google Maps, and other live information services. Research findings can then remain in the same environment as uploaded documents and previously collected knowledge.&lt;/p&gt;

&lt;p&gt;It is less purely focused on the research algorithm than GPT Researcher or STORM. A significant part of SurfSense's value comes from retrieval infrastructure, connectors, knowledge management, and downstream artifacts rather than an explicit recursively expanding research graph.&lt;/p&gt;

&lt;p&gt;Self-hosting is supported through Docker-oriented installation, and local models can be connected through common local inference interfaces. The current repository is Apache-2.0 licensed. Choose SurfSense when the difficult part of research is obtaining, structuring, retaining, and reusing information from many different data sources.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vane, Formerly Perplexica
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/ItzCrazyKns/Vane" rel="noopener noreferrer"&gt;Vane&lt;/a&gt;, formerly known as Perplexica, is an open-source AI answering engine designed as a self-hosted alternative to search-first products such as Perplexity. It combines an AI chat interface, search backend, citations, local model support, and semantic search over uploaded files. For the Docker quickstart, &lt;code&gt;SEARXNG_API_URL&lt;/code&gt; wiring, and Ollama/llama.cpp setup, see &lt;a href="https://www.glukhov.org/llm-hosting/llm-frontends/vane-perplexica-2/" rel="noopener noreferrer"&gt;Vane (Perplexica 2.0) Quickstart With Ollama and llama.cpp&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Vane is good at the search-and-answer experience. It classifies questions, conducts web research, retrieves useful information, and generates cited responses through a polished interface. Most deployments back it with &lt;a href="https://www.glukhov.org/data-infrastructure/search/selfhosting-searxng/" rel="noopener noreferrer"&gt;SearXNG&lt;/a&gt; as the search layer. For users who mainly want a private AI search engine backed by SearXNG and local models, it provides a much more focused experience than a large general agent platform.&lt;/p&gt;

&lt;p&gt;Its limitation in this comparison is research depth. Although the system can run research operations, its architecture is still primarily that of an answering engine rather than a recursively branching Deep Research framework. It should therefore not be treated as equivalent to GPT Researcher merely because both can perform multiple searches before responding.&lt;/p&gt;

&lt;p&gt;Vane is relatively straightforward to deploy with Docker and supports common model providers and local inference systems. It is MIT licensed. Choose Vane when the primary requirement is high-quality self-hosted AI search with citations rather than long-running autonomous investigation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deep Research by lukeswade
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/lukeswade/deep-research" rel="noopener noreferrer"&gt;lukeswade/deep-research&lt;/a&gt; project is a smaller self-hosted research system, but it implements one of the more interesting workflows in this comparison. It supports both cloud models and local OpenAI-compatible endpoints including llama.cpp, LM Studio, Ollama, vLLM, and MLX - for the server side of that endpoint, see &lt;a href="https://www.glukhov.org/llm-hosting/llama-cpp/" rel="noopener noreferrer"&gt;llama.cpp Quickstart with CLI and Server&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Its research loop is explicitly gap driven. A run decomposes the question into targeted searches, reads relevant pages, produces per-source notes containing evidence, and analyzes what remains unknown before deciding what should be searched next. Higher depth settings allow several rounds and progressively larger source budgets, while saturation detection can stop research early when new searches cease producing useful information.&lt;/p&gt;

&lt;p&gt;It does not have the ecosystem, organizational connectors, or general AI workstation capabilities of Onyx, Open WebUI, or Unsloth Studio. It is much more narrowly focused on doing one thing: researching a question deeply and storing the resulting research in a searchable local library.&lt;/p&gt;

&lt;p&gt;That focus also makes deployment relatively understandable. The system has a web UI and is particularly friendly to local OpenAI-compatible model servers; the documentation recommends capable models and supports using a smaller fast model for high-volume note processing. It is MIT licensed. Choose it when local inference, transparent evidence collection, and information-gap-driven research matter more than a large surrounding platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which Deep Research System Should You Choose?
&lt;/h2&gt;

&lt;p&gt;There is no single winner, because these systems solve somewhat different problems.&lt;/p&gt;

&lt;p&gt;For an explicit research algorithm with understandable depth controls, &lt;strong&gt;GPT Researcher&lt;/strong&gt; remains one of the clearest starting points. Its breadth/depth model makes it easy to reason about why research expands and how expensive a run may become.&lt;/p&gt;

&lt;p&gt;For a strongly local workflow, &lt;strong&gt;Local Deep Research&lt;/strong&gt; and &lt;strong&gt;Unsloth Studio&lt;/strong&gt; are particularly attractive. Local Deep Research provides more research-strategy flexibility, while Unsloth Studio integrates research with model management, inference, RAG, and the broader local-model workflow.&lt;/p&gt;

&lt;p&gt;For long-form knowledge curation, &lt;strong&gt;STORM&lt;/strong&gt; remains unusually interesting because its multi-perspective question-generation technique attacks a problem that many research systems ignore: discovering the questions that the original user did not know to ask.&lt;/p&gt;

&lt;p&gt;For multi-agent systems, &lt;strong&gt;DeerFlow&lt;/strong&gt; represents a different direction. Instead of building a specialized research loop, it treats research as a long-running agent task that can be delegated among subagents and combined with tools, memory, code execution, and other capabilities.&lt;/p&gt;

&lt;p&gt;For organizations, &lt;strong&gt;Onyx&lt;/strong&gt; has one of the strongest combinations of Deep Research, RAG, web investigation, and enterprise knowledge connectors. Its heavier infrastructure is justified when internal information sources matter as much as the public web.&lt;/p&gt;

&lt;p&gt;For an existing local AI installation, &lt;strong&gt;Open WebUI&lt;/strong&gt; may be all that is necessary. A sufficiently capable local model with native tool calling can repeatedly search, read pages, follow links, verify information, and fill gaps without installing a separate research engine.&lt;/p&gt;

&lt;p&gt;Finally, &lt;strong&gt;lukeswade/deep-research&lt;/strong&gt; is worth watching precisely because it is smaller. Its gap-driven workflow is conceptually clean, supports llama.cpp directly through an OpenAI-compatible endpoint, and separates expensive planning and synthesis from high-volume per-source processing.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Tell Real Deep Research from Repeated Search
&lt;/h2&gt;

&lt;p&gt;The most useful way to evaluate these systems is not to ask whether they have a button labelled "Deep Research." Instead, inspect what happens after the first round of information is collected.&lt;/p&gt;

&lt;p&gt;A genuine research system should be able to discover that its original plan was incomplete. It should recognize a contradiction, missing source, unexpected implementation detail, or newly relevant subtopic and change its subsequent investigation accordingly. That is the line separating sophisticated retrieval from research.&lt;/p&gt;

&lt;p&gt;For self-hosting, the ecosystem is now broad enough that the choice is no longer simply between a cloud Deep Research service and a home-grown script. There are dedicated recursive researchers, academic knowledge-curation systems, enterprise research platforms, local-model workstations, general agent frameworks, and lightweight gap-driven tools.&lt;/p&gt;

&lt;p&gt;The right choice therefore depends less on which project advertises the most features and more on the research architecture you want to operate: explicit recursion, multi-agent delegation, evidence-gap analysis, perspective discovery, or model-driven autonomous search.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/assafelovic/gpt-researcher" rel="noopener noreferrer"&gt;GPT Researcher&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/unslothai/unsloth" rel="noopener noreferrer"&gt;Unsloth Studio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/LearningCircuit/local-deep-research" rel="noopener noreferrer"&gt;Local Deep Research&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/stanford-oval/storm" rel="noopener noreferrer"&gt;STORM / Co-STORM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/bytedance/deer-flow" rel="noopener noreferrer"&gt;DeerFlow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/onyx-dot-app/onyx" rel="noopener noreferrer"&gt;Onyx&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/langchain-ai/open_deep_research" rel="noopener noreferrer"&gt;Open Deep Research&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/open-webui/open-webui" rel="noopener noreferrer"&gt;Open WebUI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/khoj-ai/khoj" rel="noopener noreferrer"&gt;Khoj&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/MODSetter/SurfSense" rel="noopener noreferrer"&gt;SurfSense&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ItzCrazyKns/Vane" rel="noopener noreferrer"&gt;Vane (formerly Perplexica)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/lukeswade/deep-research" rel="noopener noreferrer"&gt;lukeswade/deep-research&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.glukhov.org/rag/architecture/search-vs-deepsearch-vs-deep-research/" rel="noopener noreferrer"&gt;Search vs Deep Search vs Deep Research in 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.glukhov.org/ai-systems/architecture/multi-agent-orchestration-patterns/" rel="noopener noreferrer"&gt;Multi-Agent Orchestration Patterns&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>deepresearch</category>
      <category>aiagents</category>
      <category>rag</category>
      <category>llm</category>
    </item>
  </channel>
</rss>
