<?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: Denis MacPherson</title>
    <description>The latest articles on DEV Community by Denis MacPherson (@dmacpherson).</description>
    <link>https://dev.to/dmacpherson</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%2F4108762%2F9b165685-2162-4df7-b80c-aa419fa137bc.png</url>
      <title>DEV Community: Denis MacPherson</title>
      <link>https://dev.to/dmacpherson</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dmacpherson"/>
    <language>en</language>
    <item>
      <title>Chaos Cypher v0.6.0: Memory for Agents That Says Who Said It</title>
      <dc:creator>Denis MacPherson</dc:creator>
      <pubDate>Fri, 30 Oct 2026 15:21:11 +0000</pubDate>
      <link>https://dev.to/chaoscypherinc/chaos-cypher-v060-memory-for-agents-that-says-who-said-it-1jod</link>
      <guid>https://dev.to/chaoscypherinc/chaos-cypher-v060-memory-for-agents-that-says-who-said-it-1jod</guid>
      <description>&lt;p&gt;An agent that works with you for a week learns a lot, and almost all of it is gone the next time it starts. The usual fixes are a notes file it rereads or a vector store it searches, and neither can tell you who said a thing, when, or what it replaced.&lt;/p&gt;

&lt;p&gt;Chaos Cypher v0.6.0 is the agent-memory release. It has three themes: memory for agents, retrieval that reads the question, and sources you can keep current. It also carries breaking changes for MCP clients, custom domain plugins and databases from before the core entity types, so if you run any of those, read the upgrade notes before you pull the image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Memory for agents
&lt;/h2&gt;

&lt;p&gt;Five new MCP tools let an agent hand Chaos Cypher a sentence or a note in plain words and get it back later as facts with where they came from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;remember&lt;/code&gt;&lt;/strong&gt; stores the text as a source of type &lt;code&gt;memory&lt;/code&gt;. It is kept whole and searchable at once, and its facts are extracted against the entities the graph already knows, so a later "Pierre" lands on the same Pierre node. A text that looks like a key or a password is refused and nothing is stored.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;recall&lt;/code&gt;&lt;/strong&gt; answers in the shape of the question — an entity card, a grouped list, a relation chain, or a digest — with every fact tagged &lt;code&gt;[who DATE]&lt;/code&gt;, &lt;code&gt;[doc TITLE]&lt;/code&gt; or &lt;code&gt;[pack NAME]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;forget&lt;/code&gt;&lt;/strong&gt; previews the matches first, then removes one memory and the facts only it stated, and leaves an audit row with the counts — never the text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;list_memories&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;digest&lt;/code&gt;&lt;/strong&gt; show what is stored and what a window of time added, replaced and forgot.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A later memory on a single-valued relation (where someone lives) replaces the earlier fact and keeps it as history, and &lt;code&gt;recall(as_of=…)&lt;/code&gt; shows the facts as they stood on a date.&lt;/p&gt;

&lt;p&gt;Three things make this practical on a small local setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No embedding model required.&lt;/strong&gt; With &lt;code&gt;search.enable_vector_search: false&lt;/code&gt;, the CLI, the Python API and &lt;code&gt;chaoscypher mcp&lt;/code&gt; make no embedding call; search answers with keyword results and every memory tool works.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Several agents, one database.&lt;/strong&gt; One &lt;code&gt;chaoscypher mcp -m write&lt;/code&gt; per agent can share a database on the same machine and a local disk. No write is lost; a write that cannot get the lock in 60 seconds answers one fixed "database is busy" line instead of hanging.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nothing is dropped when linking is slow.&lt;/strong&gt; &lt;code&gt;remember&lt;/code&gt; stores the memory before it links it into the graph; if the model does not answer in time, the memory is kept as a note, says why, and links later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;chaoscypher mcp --profile memory&lt;/code&gt; serves only these five tools, which keeps a small model's tool list short, and &lt;code&gt;chaoscypher memory export&lt;/code&gt; writes memories to a &lt;code&gt;.ccx&lt;/code&gt; package another database can load. The &lt;a href="https://chaoscypher.com/docs/user-guide/memory" rel="noopener noreferrer"&gt;Memory guide&lt;/a&gt; walks through it.&lt;/p&gt;

&lt;p&gt;In plain terms: your agent can now keep what it learns between sessions, and every fact it recalls tells you who said it and when.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retrieval that reads the question
&lt;/h2&gt;

&lt;p&gt;Before chat builds its graph context, it now decides what kind of question it was asked: a lookup, an aggregate (everything sharing a relation with a named entity), a path (how two named things are connected) or a global question. An aggregate gets the entity's relationships grouped by label, each with the sentence that states it; a path gets the shortest relation chains between the two entities, each hop with its sentence. The default router uses rules, with no extra model call.&lt;/p&gt;

&lt;p&gt;The graph is also handed to the model in a tighter shape (40 triples instead of 200 by default), an identity resolver merges one entity written under two names after every commit, and hybrid search gains a real keyword half: chunks now have a full-text index.&lt;/p&gt;

&lt;p&gt;In plain terms: chat looks for the kind of evidence your question needs, and shows the model the sentences behind it rather than a wall of triples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources you can keep current
&lt;/h2&gt;

&lt;p&gt;Paste a documentation site's address and each of its pages is imported as its own source under one site, found through the site's &lt;code&gt;llms.txt&lt;/code&gt;, its sitemaps, or a three-link-deep crawl that never leaves the address you gave. &lt;strong&gt;Check for updates&lt;/strong&gt; re-fetches every page: unchanged pages are left alone, changed ones re-imported in place, new ones added, and a page the site no longer lists is marked &lt;strong&gt;Gone from site&lt;/strong&gt;, never deleted. Pages you left out stay out. The crawler sends its own user agent and honours &lt;code&gt;robots.txt&lt;/code&gt;. See &lt;a href="https://chaoscypher.com/docs/user-guide/site-import" rel="noopener noreferrer"&gt;Import a Docs Site&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In plain terms: a docs site becomes one source you can refresh, and your citations keep pointing at the page they came from.&lt;/p&gt;

&lt;h2&gt;
  
  
  Also in this release
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nothing that says something is deleted by a filter.&lt;/strong&gt; Entities and relationships the extraction filters used to drop are now kept with a flag (&lt;code&gt;unevidenced&lt;/code&gt;, &lt;code&gt;low_plausibility&lt;/code&gt;) when they carry a description or evidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pinned decoding by default:&lt;/strong&gt; temperature 0 and seed 42 for chat and extraction, so re-imports and retries give the same output for the same input (only Ollama honours the seed). Regenerate still gives a different answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security:&lt;/strong&gt; the PyPI packages carry PEP 740 attestations, chat runs only the tools it offered the model, and a failed MCP tool call answers a fixed sentence and a code, never the error's own text.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Upgrading
&lt;/h2&gt;

&lt;p&gt;Upgrade with the published 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 pull ghcr.io/chaoscypherinc/chaoscypher:0.6.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or &lt;code&gt;pip install -U chaoscypher-cli&lt;/code&gt;. Before you swap the image, drain the queue (wait for &lt;code&gt;/api/v1/queue/stats&lt;/code&gt; to report 0 pending). Eighteen migrations run on first start, six of them &lt;code&gt;needs_confirmation&lt;/code&gt;. Read &lt;strong&gt;Breaking changes&lt;/strong&gt; and &lt;strong&gt;Upgrade advisory&lt;/strong&gt; in the &lt;a href="https://chaoscypher.com/docs/about/changelog#v060-2026-10-27" rel="noopener noreferrer"&gt;v0.6.0 changelog&lt;/a&gt; first if you run custom domain plugins, MCP clients that match error strings, API clients that threshold the hybrid search &lt;code&gt;score&lt;/code&gt;, or a database extracted before the core entity types — that last one is not migrated in place: export with v0.5.0, upgrade, then import.&lt;/p&gt;

&lt;p&gt;New here? The &lt;a href="https://chaoscypher.com/blog/graphrag-ollama-10-minutes" rel="noopener noreferrer"&gt;ten-minute quickstart&lt;/a&gt; runs the whole stack locally with Ollama, and nothing leaves your machine unless you choose.&lt;/p&gt;

</description>
      <category>selfhosted</category>
      <category>opensource</category>
      <category>ai</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Build It Once, Mount It Anywhere: One Knowledge Package, Any MCP Client</title>
      <dc:creator>Denis MacPherson</dc:creator>
      <pubDate>Fri, 30 Oct 2026 15:17:52 +0000</pubDate>
      <link>https://dev.to/chaoscypherinc/build-it-once-mount-it-anywhere-one-knowledge-package-any-mcp-client-26hb</link>
      <guid>https://dev.to/chaoscypherinc/build-it-once-mount-it-anywhere-one-knowledge-package-any-mcp-client-26hb</guid>
      <description>&lt;p&gt;The local AI stack has a runtime, a model format, a model hub, and an agent protocol. It does not yet have a portable knowledge layer. The graph you spent an afternoon building is trapped in whichever app built it, and the assistant you actually work in cannot see it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;chaoscypher mount&lt;/code&gt; is the smallest thing we could ship that fixes that. One command pulls a knowledge package, imports it into a database of its own, indexes it, and serves it over MCP. Put that command in an assistant's config and the assistant has the knowledge every time it launches.&lt;/p&gt;

&lt;p&gt;This post builds a package on one machine, moves it to another, and mounts the same file into two different assistants, with the same command ready for any other MCP client. Nothing about the package changes between them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You'll Build
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A knowledge graph extracted from a set of documents, exported as a single &lt;code&gt;.ccx&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;That file mounted into Claude Desktop and Claude Code on a second machine&lt;/li&gt;
&lt;li&gt;The same multi-hop question answered by both, each citing from the same evidence base&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The package format is &lt;a href="https://chaoscypher.com/docs/reference/ccx-format" rel="noopener noreferrer"&gt;CCX&lt;/a&gt;, an open, documented format with a standalone reader. What travels is the graph, the source text, the chunks, and the citations. What does not travel is any dependency on the machine that built it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Build the graph, then export it
&lt;/h2&gt;

&lt;p&gt;On the build machine, run Chaos Cypher and add your documents. The &lt;a href="https://chaoscypher.com/blog/graphrag-ollama-10-minutes" rel="noopener noreferrer"&gt;ten-minute quickstart&lt;/a&gt; covers this with Ollama; the &lt;a href="https://chaoscypher.com/blog/claude-builds-its-own-knowledge-graph" rel="noopener noreferrer"&gt;previous post&lt;/a&gt; does it with Claude doing the extraction. Either way you end up with a graph in a database.&lt;/p&gt;

&lt;p&gt;Export it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;chaoscypher graph package &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; research.ccx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default export carries everything a mount needs: templates, entities, relationships, sources, chunks, and citations. The archive is deterministic and checksummed, so the same graph produces the same bytes and a tampered file fails validation on the other side.&lt;/p&gt;

&lt;p&gt;Copy &lt;code&gt;research.ccx&lt;/code&gt; to the second machine however you like, or publish it to the &lt;a href="https://chaoscypher.com/docs/lexicon-hub" rel="noopener noreferrer"&gt;Lexicon Hub&lt;/a&gt; with &lt;code&gt;chaoscypher push research.ccx&lt;/code&gt; and pull it by name.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Mount it
&lt;/h2&gt;

&lt;p&gt;On the second machine, install the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pipx &lt;span class="nb"&gt;install &lt;/span&gt;chaoscypher-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then mount the package once, without serving, to see what it does:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;chaoscypher mount ./research.ccx &lt;span class="nt"&gt;--no-serve&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the real output for a package built from fourteen of Chaos Cypher's own architecture pages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Importing: archdocs.ccx
✓ Mounted ./archdocs.ccx → database archdocs: 177 entities, 205 relationships, 14 sources, 178 citations
Indexing for search with the local embedding model (Qwen/Qwen3-Embedding-0.6B); the first use downloads it.
✓ Indexed 14 sources and 177 nodes for search

Serve it with: chaoscypher mcp --database archdocs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things happened. The package went into a database named after it, so it does not mix with anything else on the machine. The sources and citations came along, not just the entities, which is what keeps answers traceable. And a &lt;code&gt;mount.json&lt;/code&gt; marker was written with the package's SHA-256, so the next mount of the same file skips the import.&lt;/p&gt;

&lt;p&gt;That last point is the whole design. An MCP host runs its server command on every launch. &lt;code&gt;mount&lt;/code&gt; is safe to run on every launch.&lt;/p&gt;

&lt;p&gt;There is no &lt;code&gt;chaoscypher setup&lt;/code&gt; in this step either. Mounting needs no chat or extraction model; the assistant's own model asks the questions. With vector search on (the default) the mount is indexed for semantic search with the configured embedding model (by default a local one, downloaded on first use); if vector search is off, it is keyword-searchable only.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Mount it into your assistants
&lt;/h2&gt;

&lt;p&gt;The command is the same each time. Only the config file changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude Desktop&lt;/strong&gt; (&lt;code&gt;claude_desktop_config.json&lt;/code&gt;):&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;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"research"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chaoscypher"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"mount"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/home/me/research.ccx"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Claude Code:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add research &lt;span class="nt"&gt;--&lt;/span&gt; chaoscypher mount /home/me/research.ccx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Any other MCP client.&lt;/strong&gt; The &lt;a href="https://chaoscypher.com/docs/user-guide/mcp" rel="noopener noreferrer"&gt;MCP user guide&lt;/a&gt; has the Cursor configuration alongside Claude's; for any client that launches a stdio MCP server, point its server list at the same &lt;code&gt;chaoscypher mount&lt;/code&gt; command. The knowledge on the other end is identical.&lt;/p&gt;

&lt;p&gt;If the package came from the Hub, replace the path with the reference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"mount"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"acme/research"&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;h2&gt;
  
  
  Step 4: Ask both the same question
&lt;/h2&gt;

&lt;p&gt;Pick a question that needs two hops, so the graph has something to do:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which components depend on the queue client, and which ADR governs changing it?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Each assistant calls &lt;code&gt;graphrag_search&lt;/code&gt;. The tool embeds the question, seeds a Personalized PageRank walk from the closest entities, and fuses two evidence lists: the chunks the top entities were cited from, and the chunks a hybrid keyword-and-vector search would return. The response carries the graph context and the chunks with aliases the assistant uses for inline citations.&lt;/p&gt;

&lt;p&gt;Because the citations traveled inside the package, every assistant cites from the same sources and chunks the graph was built from. Each host's model picks which chunks to cite; the evidence it picks from is identical. Two assistants, one set of receipts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In plain English:&lt;/strong&gt; you built the knowledge once. Every assistant that mounts it gets the same facts and the same evidence, without re-reading a single document.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a mount and not an import
&lt;/h2&gt;

&lt;p&gt;Chaos Cypher already had &lt;code&gt;chaoscypher graph package load&lt;/code&gt;, which merges a package into the database you are working in. That is the right tool when you want the knowledge in &lt;em&gt;your&lt;/em&gt; graph, alongside everything else you have built.&lt;/p&gt;

&lt;p&gt;A mount is different in two ways. It goes into its own database, so it cannot collide with your work and can be thrown away with &lt;code&gt;chaoscypher db delete&lt;/code&gt;. And it is idempotent, so it belongs in a config file rather than a runbook.&lt;/p&gt;

&lt;p&gt;The metaphor is deliberate. You mount a volume; you do not copy its contents into your home directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does not do yet
&lt;/h2&gt;

&lt;p&gt;A mount is read-only by default. Pass &lt;code&gt;--mode write&lt;/code&gt; if you want the assistant to be able to add to the mounted database, but the package on disk does not change when it does. There is no &lt;code&gt;unmount&lt;/code&gt; command; deleting the database is the unmount. And the default export leaves embedding vectors out, so with vector search on, the mounting machine re-embeds the package with whatever embedding model is configured there; that happens once, on the first launch, and takes a moment. With vector search off, nothing is embedded and the mount is keyword-only. Pass &lt;code&gt;--embeddings&lt;/code&gt; at export time if both machines use the same model and you want to skip it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://chaoscypher.com/docs/reference/cli/mount" rel="noopener noreferrer"&gt;Mount command reference&lt;/a&gt; lists every option and the mount-versus-load comparison.&lt;/li&gt;
&lt;li&gt;Packages compose. An &lt;code&gt;axiomatize.yaml&lt;/code&gt; can merge several packages into one served database with &lt;code&gt;chaoscypher compose&lt;/code&gt;. That is a post of its own.&lt;/li&gt;
&lt;li&gt;If you publish a package to the Hub, the same &lt;code&gt;mount&lt;/code&gt; line works for everyone who pulls it.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>graphrag</category>
      <category>mcp</category>
      <category>selfhosted</category>
      <category>opensource</category>
    </item>
    <item>
      <title>GraphRAG with Ollama in 10 Minutes: The Local-First Quickstart</title>
      <dc:creator>Denis MacPherson</dc:creator>
      <pubDate>Mon, 28 Sep 2026 14:50:48 +0000</pubDate>
      <link>https://dev.to/chaoscypherinc/graphrag-with-ollama-in-10-minutes-the-local-first-quickstart-48h5</link>
      <guid>https://dev.to/chaoscypherinc/graphrag-with-ollama-in-10-minutes-the-local-first-quickstart-48h5</guid>
      <description>&lt;p&gt;You don't need a cloud API key to try GraphRAG. Ten minutes, one document, and a laptop with a decent GPU (or none at all, for the first few steps) is enough to see a full knowledge graph form and answer a question with a citation you can click through to the source page.&lt;/p&gt;

&lt;p&gt;This is the fast version. If you want the reasoning behind running everything locally -- privacy, VRAM presets, multi-GPU load balancing -- that's in &lt;a href="https://chaoscypher.com/blog/local-ai-knowledge-graph" rel="noopener noreferrer"&gt;Build a Private AI Knowledge Graph That Never Leaves Your Machine&lt;/a&gt;. This post is the "just show me" version: install, upload, watch the graph build, ask a question, done.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You'll Build
&lt;/h2&gt;

&lt;p&gt;By the end of this walkthrough you'll have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chaos Cypher running locally via Docker&lt;/li&gt;
&lt;li&gt;A local LLM (Ollama) doing the entity extraction and chat&lt;/li&gt;
&lt;li&gt;One document imported, chunked, and embedded&lt;/li&gt;
&lt;li&gt;A knowledge graph of entities and relationships extracted from that document&lt;/li&gt;
&lt;li&gt;A chat answer with a citation that traces back to the exact chunk it came from&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing leaves your machine. No OpenAI key, no Anthropic key, no usage bill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install Ollama and Start the Model Download
&lt;/h2&gt;

&lt;p&gt;Grab Ollama from &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;ollama.com&lt;/a&gt; for your platform, then pull the default chat/extraction model in a terminal:&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:30b-instruct
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the long pole of the whole setup -- roughly 18-20 GB. Kick it off now and let it run in the background while you do the next steps; indexing and search don't need it, only extraction and chat do.&lt;/p&gt;

&lt;p&gt;If you're on a smaller GPU or just want to see the pipeline move faster for this first run, point &lt;code&gt;ollama_chat_model&lt;/code&gt; at a smaller &lt;code&gt;qwen3&lt;/code&gt; tag instead -- you can always switch to the full model afterward from Settings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Start Chaos Cypher
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; chaoscypher &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 80:80 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 443:443 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; chaoscypher-data:/data &lt;span class="se"&gt;\&lt;/span&gt;
  ghcr.io/chaoscypherinc/chaoscypher:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Linux Docker Engine (not Docker Desktop), add &lt;code&gt;--add-host=host.docker.internal:host-gateway&lt;/code&gt; so the container can reach Ollama on your host -- Docker Desktop resolves this automatically.&lt;/p&gt;

&lt;p&gt;Open &lt;a href="http://localhost" rel="noopener noreferrer"&gt;http://localhost&lt;/a&gt;. A startup page shows each service (Nginx, Cortex, Valkey, Neuron) coming online -- usually 30-60 seconds. Set a username and password on first run, and you land on the Dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Upload a Document
&lt;/h2&gt;

&lt;p&gt;Go to &lt;strong&gt;Sources&lt;/strong&gt; in the sidebar and drag in a PDF, DOCX, or text file -- a 10-20 page document is a good first run so you can watch the whole pipeline complete in a couple of minutes.&lt;/p&gt;

&lt;p&gt;Indexing starts immediately (chunking + embedding for search) and finishes in about 30 seconds per 100 pages -- this step downloads a small embedding model (~600 MB) the first time, but doesn't need Ollama at all. Once indexing finishes, a &lt;strong&gt;Review&lt;/strong&gt; dialog proposes an extraction domain (technical, medical, legal, and so on) detected from the content. Click &lt;strong&gt;Confirm&lt;/strong&gt; to queue entity extraction.&lt;/p&gt;

&lt;p&gt;If the Ollama model pull from Step 1 is still running, the source will sit at &lt;strong&gt;extracting&lt;/strong&gt; until it finishes -- indexing and search still work while you wait.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Watch the Graph Form
&lt;/h2&gt;

&lt;p&gt;Once extraction commits, open &lt;strong&gt;Graph&lt;/strong&gt; in the sidebar. You'll see nodes for the people, organizations, concepts, and events the model found in your document, connected by edges representing the relationships it inferred between them. For a 100-page document on a 30B-class model, expect roughly 5-10 minutes of extraction time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F02uolct3m935zqakcdhn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F02uolct3m935zqakcdhn.png" alt="Knowledge graph visualization showing extracted entities and relationships" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click any node to see its properties, its connections, and the source text it was extracted from -- that link back to source evidence is the same mechanism the chat citations use in the next step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Ask a Cited Question
&lt;/h2&gt;

&lt;p&gt;Open &lt;strong&gt;Chat&lt;/strong&gt;, start a new conversation, and ask something specific about the document you just uploaded. Chaos Cypher retrieves the relevant chunks and graph context, hands them to your local model, and streams back an answer with citations attached to the sentences that came from your source.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fax02jopz0ihmszz4b587.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fax02jopz0ihmszz4b587.png" alt="Chat page with an AI answer ranking the top entities by PageRank, with entity chips" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click a citation and it jumps straight to the exact chunk in the source document the sentence was grounded in -- not a link to "the document," the specific paragraph. That's the difference between an AI answer you have to take on faith and one you can verify in a click.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to Go From Here
&lt;/h2&gt;

&lt;p&gt;That's the whole loop: install, upload, extract, ask, verify. A few directions once you're comfortable with it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bigger documents, bigger graphs.&lt;/strong&gt; Upload a set of related documents as a single archive (a zip) and the graph connects entities across everything inside it. Today each separate upload builds its own graph; cross-source entity resolution is on the roadmap, not in the box.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tune for your hardware.&lt;/strong&gt; The VRAM preset table and multi-instance GPU setup in the &lt;a href="https://chaoscypher.com/blog/local-ai-knowledge-graph" rel="noopener noreferrer"&gt;privacy-first deep dive&lt;/a&gt; cover getting the most out of local hardware.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mix local and cloud.&lt;/strong&gt; Keep chat and search local for privacy, and switch just the extraction pass to a cloud provider if you want maximum quality on a hard document -- it's a single settings change, same pipeline either way.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In plain English: you can run a real knowledge graph, end to end, on your own machine, in about the time it takes to read this post.&lt;/p&gt;

</description>
      <category>selfhosted</category>
      <category>graphrag</category>
      <category>ollama</category>
      <category>python</category>
    </item>
    <item>
      <title>Claude Built Its Own Knowledge Graph. No Local LLM Was Involved.</title>
      <dc:creator>Denis MacPherson</dc:creator>
      <pubDate>Mon, 28 Sep 2026 14:50:30 +0000</pubDate>
      <link>https://dev.to/chaoscypherinc/claude-built-its-own-knowledge-graph-no-local-llm-was-involved-nij</link>
      <guid>https://dev.to/chaoscypherinc/claude-built-its-own-knowledge-graph-no-local-llm-was-involved-nij</guid>
      <description>&lt;p&gt;Every GraphRAG tool I know of has the same shape: the server owns a language model, and the server calls it to turn documents into a graph. That is fine when you have a GPU and a model you like. It is a problem when the best model you have access to is the one already sitting in your editor, and it has no idea your knowledge base exists.&lt;/p&gt;

&lt;p&gt;Chaos Cypher's MCP server flips the direction. The assistant on the other end of the connection does the extraction. Claude Code reads the chunks, decides what the entities and relationships are, submits them back, and commits the graph. The server never calls a model. When it is done, the same assistant can ask the graph questions it could not have answered from its context window.&lt;/p&gt;

&lt;p&gt;This post walks through that flow end to end with Claude Code. It works the same way from Claude Desktop, Cursor, or any MCP client whose model can follow a short set of instructions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You'll Build
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Chaos Cypher's MCP server connected to Claude Code, in write mode&lt;/li&gt;
&lt;li&gt;A knowledge graph built from a folder of your own markdown, extracted by Claude itself&lt;/li&gt;
&lt;li&gt;A persistent graph on your disk that survives closing the session&lt;/li&gt;
&lt;li&gt;A multi-hop question answered from the graph, with citations back to the sentences that support it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The only model involved is the one Claude Code is already using. Chaos Cypher computes the chunk embeddings locally with sentence-transformers on CPU, and nothing else runs a model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install the CLI and connect it to Claude Code
&lt;/h2&gt;

&lt;p&gt;The MCP server ships in the standalone CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pipx &lt;span class="nb"&gt;install &lt;/span&gt;chaoscypher-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CLI needs Python 3.14 or newer. Then register the server with Claude Code in &lt;strong&gt;write&lt;/strong&gt; mode, because building a graph means adding documents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add chaoscypher &lt;span class="nt"&gt;--&lt;/span&gt; chaoscypher mcp &lt;span class="nt"&gt;--mode&lt;/span&gt; write
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole setup. There is no &lt;code&gt;chaoscypher setup&lt;/code&gt; step here on purpose: the wizard configures a chat and extraction model for the server, and this flow does not use one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Ask Claude to build the graph
&lt;/h2&gt;

&lt;p&gt;Open Claude Code in a project that has some prose in it — design notes, ADRs, meeting notes, a docs folder. Then ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Add every markdown file under &lt;code&gt;docs/&lt;/code&gt; to the Chaos Cypher knowledge graph, extract the entities and relationships yourself, and tell me the quality grade when you're done.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is what happens next, tool by tool. You can watch it in Claude Code's tool log.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;add_document&lt;/code&gt;&lt;/strong&gt; takes a file path (or a URL), chunks the text, and embeds the chunks locally. By default it only indexes; it does not ask the server to extract anything. The tool description tells the assistant exactly what to do next, which is why a one-line prompt is enough.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;confirm_extraction&lt;/code&gt;&lt;/strong&gt; clears the domain gate. Chaos Cypher auto-detects an extraction domain for each source — technical, scientific, legal, and so on — and parks the source until someone confirms it. Claude confirms it, or overrides it if you told it to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;get_extraction_tasks&lt;/code&gt;&lt;/strong&gt; returns the plan: how many chunk groups there are and the extraction instructions, including the output format Claude is expected to produce.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;get_extraction_chunks&lt;/code&gt;&lt;/strong&gt; fetches the text of a chunk group with &lt;strong&gt;numbered sentences&lt;/strong&gt;. This is the part that matters for trust. Every entity and relationship Claude submits has to point at the sentence numbers that support it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;submit_chunk_extraction&lt;/code&gt;&lt;/strong&gt; is where Claude does the work. The format is a compact line protocol rather than JSON, one entity per line with its properties following, then one relationship per line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;E|Alice Smith|Person|Alice; A. Smith|0.95|S1|Lead engineer on the ingest service
P|0|role|Lead Engineer
E|Ingest Service|Component|ingest-svc|0.9|S1-S2|Handles uploads and chunking
R|0|1|works_on|0.9|S1|Alice Smith leads the ingest service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;S1&lt;/code&gt; and &lt;code&gt;S1-S2&lt;/code&gt; fields are sentence references. On the server, an evidence validator checks each one against the numbered text. An entity that does not appear in the sentence it cites is dropped, and the drop is counted so you can see it later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;finalize_extraction&lt;/code&gt;&lt;/strong&gt; deduplicates entities across chunks, matches them to templates, writes the citations, and commits the graph. It returns a quality grade on a 0 to 100 scale with the breakdown behind it.&lt;/p&gt;

&lt;p&gt;That is the whole loop. For a folder of thirty markdown files it takes Claude a few minutes, and the tool log shows every submission.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In plain English:&lt;/strong&gt; Claude reads your documents in numbered chunks, writes down what it found and which sentences prove it, and Chaos Cypher checks the receipts before committing anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Look at what it built
&lt;/h2&gt;

&lt;p&gt;The graph is an ordinary Chaos Cypher database in your data directory. Start the local server and open it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;chaoscypher serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every entity is typed, every relationship has a justification, and every one of them links back to the source chunk and the sentence Claude cited. The source page shows the pipeline for each file: loaded, chunked, extracted, indexed, with the quality breakdown and a count of anything the evidence check rejected.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F58w00hqzu0lp1cdoj8iw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F58w00hqzu0lp1cdoj8iw.png" alt="Source overview showing the pipeline stages, extraction counts, and entity distribution for one document" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbiqsfwzj9ink4glhoqsu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbiqsfwzj9ink4glhoqsu.png" alt="Knowledge graph canvas with typed, color-coded entities clustered around their source documents" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If a relationship looks wrong, you can edit or delete it here. The graph is yours to correct, not a black box you have to trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Close the session. Open a new one. Ask a hard question.
&lt;/h2&gt;

&lt;p&gt;This is the part that makes the whole thing worth doing. Quit Claude Code and start it again. The context window is empty. The graph is not.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Using the Chaos Cypher graph, which components depend on the queue client, and which ADR governs changing it?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Claude calls &lt;strong&gt;&lt;code&gt;graphrag_search&lt;/code&gt;&lt;/strong&gt;. That tool embeds the question, finds the entities closest to it, runs Personalized PageRank over the graph from those seeds, and pulls two kinds of evidence: the chunks that the top-ranked entities were &lt;em&gt;cited from&lt;/em&gt;, and the chunks a plain vector search would have returned. The two lists are fused with Reciprocal Rank Fusion and handed back with the graph context, so the answer names the components, the ADR, and the chunks each fact came from.&lt;/p&gt;

&lt;p&gt;A question like that needs two hops: from the queue client to the things that depend on it, and from there to the decision record that governs them. Vector search over chunks tends to return the three chunks that mention "queue client" and stop. The graph walks the edges.&lt;/p&gt;

&lt;p&gt;Chaos Cypher also degrades honestly. If the embedding model is unavailable the tool falls back to keyword search and says so in the response's retrieval mode, rather than returning nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is different
&lt;/h2&gt;

&lt;p&gt;Three things make client-driven extraction more than a party trick.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The server needs no model.&lt;/strong&gt; The assistant you already pay for, or the local one you already run in your editor, does the extraction. A laptop with no GPU can build a graph with Claude doing the reading.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The evidence check runs on the server, not in the prompt.&lt;/strong&gt; Whatever the assistant submits is validated against the numbered sentences before it is committed. The graph records what was rejected and why. You do not have to trust the model's confidence number; you can look at the sentence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The graph outlives the session.&lt;/strong&gt; It sits in a SQLite database on your disk. It is inspectable in the UI, exportable as a &lt;code&gt;.ccx&lt;/code&gt; package, and available to the next session, the next tool, or the next machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does not do yet
&lt;/h2&gt;

&lt;p&gt;Extraction quality is the assistant's quality. A weak model will submit weak extractions; the evidence check catches fabrications, not shallow reading. The server also rate-limits submissions per source (100 per minute by default) and caps each submission's size, so a very large corpus is best fed in batches. And &lt;code&gt;add_document&lt;/code&gt; takes one file at a time; for a whole folder, the assistant loops, or you zip the folder and add the archive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The full tool list, with the client-driven extraction limits, is in the &lt;a href="https://chaoscypher.com/docs/user-guide/mcp" rel="noopener noreferrer"&gt;MCP user guide&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Once the graph is built, &lt;a href="https://chaoscypher.com/docs/user-guide/import-export" rel="noopener noreferrer"&gt;export it as a &lt;code&gt;.ccx&lt;/code&gt; package&lt;/a&gt; and mount it into another assistant with one command. That is the next post.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>graphrag</category>
      <category>ai</category>
      <category>selfhosted</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Chaos Cypher v0.5.0: Mount a Knowledge Package Straight Into Your MCP Host</title>
      <dc:creator>Denis MacPherson</dc:creator>
      <pubDate>Mon, 28 Sep 2026 14:49:57 +0000</pubDate>
      <link>https://dev.to/chaoscypherinc/chaos-cypher-v050-mount-a-knowledge-package-straight-into-your-mcp-host-41nj</link>
      <guid>https://dev.to/chaoscypherinc/chaos-cypher-v050-mount-a-knowledge-package-straight-into-your-mcp-host-41nj</guid>
      <description>&lt;p&gt;v0.5.0 is the first feature release since v0.4.0: 30 commits since v0.4.3, 19 of them changing product code. Two themes run through it. &lt;strong&gt;Knowledge in motion&lt;/strong&gt; — a package you build can be mounted straight into an MCP host as a one-line config entry, or stacked with other packages into a single database your agent queries. And &lt;strong&gt;measurement&lt;/strong&gt; — two benchmark boards that score local models pass/fail without a judge model, a &lt;code&gt;/leaderboard&lt;/code&gt; page that blends them into one Overall score, and model pickers in the app that read the same numbers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F02uolct3m935zqakcdhn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F02uolct3m935zqakcdhn.png" alt="Knowledge graph visualization showing extracted entities and relationships" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;chaoscypher mount&lt;/code&gt; — a package, served over MCP, in one command
&lt;/h2&gt;

&lt;p&gt;The gap this closes: you build a knowledge graph on one machine, export it as a &lt;code&gt;.ccx&lt;/code&gt; package, and then have to write a setup script on the other machine before an assistant can use it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;chaoscypher mount&lt;/code&gt; is one idempotent command. It resolves a &lt;code&gt;.ccx&lt;/code&gt; — a local file, or a Lexicon Hub &lt;code&gt;owner/name&lt;/code&gt; — imports it into a database of its own (templates, knowledge, workflows &lt;strong&gt;and&lt;/strong&gt; sources, chunks and citations), indexes it for search, and hands that database to the stdio MCP server. A &lt;code&gt;mount.json&lt;/code&gt; SHA-256 marker makes every later launch skip the import, which is the property that matters: the command belongs directly in an MCP host's config rather than in a setup script that runs once and is never thought about again.&lt;/p&gt;

&lt;p&gt;It is first-run-safe. No chat or extraction model is needed on the serving side, because the host's model asks the questions. All output goes to stderr, since stdout is the JSON-RPC channel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In plain English:&lt;/strong&gt; you can paste one command into Claude Desktop's config and the graph is there, with its citations, on every launch.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;chaoscypher compose&lt;/code&gt; now works on CCX 3.0
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;compose&lt;/code&gt; was a pre-3.0 prototype, and it failed quietly in three separate ways: the merger read a &lt;code&gt;data/*.json&lt;/code&gt; layout that no package has, so every build produced 0 entities; &lt;code&gt;up&lt;/code&gt; launched a Cortex entrypoint that exited 0 at import time; and &lt;code&gt;down&lt;/code&gt; could not stop a detached server.&lt;/p&gt;

&lt;p&gt;The merger now imports each resolved package into a real runtime database through &lt;code&gt;CcxImporter&lt;/code&gt; — templates, knowledge, sources, chunks, citations and IRIs intact — counts totals from that database, records its inputs in &lt;code&gt;composition.json&lt;/code&gt;, and runs a best-effort embedding and vector indexing pass afterwards. &lt;code&gt;up&lt;/code&gt; launches Cortex the way &lt;code&gt;serve&lt;/code&gt; does, logs to &lt;code&gt;&amp;lt;output&amp;gt;/server.log&lt;/code&gt;, watches the child through a startup grace period and fails with the log tail if it dies. &lt;code&gt;--detach&lt;/code&gt; records &lt;code&gt;compose.pid&lt;/code&gt; and refuses to stack a second live server. &lt;code&gt;down&lt;/code&gt; does SIGTERM then SIGKILL with the configured grace, cleans stale records, and says "nothing running" instead of claiming success.&lt;/p&gt;

&lt;p&gt;Two new subcommands come with it. &lt;code&gt;compose init [--name] [PACKAGES…]&lt;/code&gt; writes a starter &lt;code&gt;axiomatize.yaml&lt;/code&gt; and refuses to overwrite one without &lt;code&gt;--force&lt;/code&gt;. &lt;code&gt;compose mcp&lt;/code&gt; builds the composition if its database is missing, then replaces the process with &lt;code&gt;chaoscypher mcp&lt;/code&gt; on the composed database — stdio passes straight through and the host sees one pid.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In plain English:&lt;/strong&gt; several knowledge packages, one database, one server your agent queries — and the commands now tell you the truth about whether it is running.&lt;/p&gt;

&lt;h2&gt;
  
  
  Citations into audio and video now carry timestamps
&lt;/h2&gt;

&lt;p&gt;The audio and video loaders joined Whisper's transcript and threw the segment times away, so a citation into a recording could say which file but never when. Chunks now carry the seconds of the recording they came from, the way PDF chunks carry a page number, and the position travels everywhere a chunk goes: GraphRAG provenance and vector chunks, &lt;code&gt;search_chunks&lt;/code&gt;, &lt;code&gt;summarize&lt;/code&gt;, the search-engine dicts and chat citation resolution. The interface renders them as time badges — "12:34" beside a chunk number, "At 1:27–2:13" on a chat citation chip.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two benchmark boards, scored without a judge
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;chaoscypher benchmark&lt;/code&gt; gains an instruction-probe tier: 65 probes across five sections, one per extraction instruction, each with a passage constructed so the correct extraction is unambiguous and checkable by eye. Probes call the real extractor with the real prompts, settings pins and line parser, so a pass means the product could use the output. A "carrier" tier splices each probe into a real ~3,000-character extraction group from the corpus, testing production density rather than production length.&lt;/p&gt;

&lt;p&gt;The second board is grounded chat. Every labelled question is scored by pure functions: the answer names the terms a correct concise answer must contain, carries no names or numbers the retrieved context does not support, leads with the right entity when two are confusable, declines when the sources cannot answer, and passes a completion gate. A judge model is now optional rather than required. The fixture is &lt;em&gt;War and Peace&lt;/em&gt; Book One — 113,934 characters against a ~7.7k retrieval window, so a question is answered from retrieval and not from the whole corpus — with 80 labelled questions covering single-hop, paraphrase, multi-hop across chapters, fine-grained confusable pairs, and out-of-scope questions with decoys.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/leaderboard&lt;/code&gt; blends the two: Model, Overall, Extraction, Chat, VRAM and Speed. Overall is 0.6 × extraction + 0.4 × chat, and the weights are written into the exported data rather than recomputed per page, so the leaderboard, the homepage teaser and the app's model pickers agree. Each score shows its counts, its pass rule, the retrieval floor and the run time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In plain English:&lt;/strong&gt; the numbers are produced by checks you can read, and every page that shows them shows the same ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  The measurement work found two things that made every earlier number suspect
&lt;/h2&gt;

&lt;p&gt;This is the part worth reading even if you never run a benchmark.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Extraction truncation was invisible four layers deep — and a truncated extraction scores &lt;em&gt;higher&lt;/em&gt; than a complete one.&lt;/strong&gt; Measured twice, independent of thinking mode: &lt;code&gt;gemma4:31b&lt;/code&gt; scored 72.09 truncated against 67.02 complete, and &lt;code&gt;qwen3.8:27b&lt;/code&gt; under a forced 300-token budget scored 76.49 against 71.25. Core computed the signal correctly the whole time; it died on the way to anyone who could act on it. The CLI ingest path received the metrics dict from &lt;code&gt;extract_single_chunk&lt;/code&gt; and discarded it, so every &lt;code&gt;chaoscypher load&lt;/code&gt; dropped truncation on the floor and &lt;code&gt;chaoscypher source get&lt;/code&gt; reported a clean ingest for a source whose chunks were cut off. A new &lt;code&gt;get_source_counters&lt;/code&gt; validates against the same allowlist the increment uses, so the read and write sets cannot drift, and the leaderboard warns by name about any model whose extraction did not run to completion.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F58w00hqzu0lp1cdoj8iw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F58w00hqzu0lp1cdoj8iw.png" alt="Source overview showing the pipeline stages, extraction counts and entity distribution for one document" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Benchmark runs advertised determinism they never applied.&lt;/strong&gt; Temperature and seed were pinned behind &lt;code&gt;hasattr&lt;/code&gt; guards keyed on field names &lt;code&gt;LLMSettings&lt;/code&gt; does not have, so both assignments silently no-opped while the result row recorded &lt;code&gt;seed=42, temperature=0.0&lt;/code&gt;. Five identical runs spanned 3.5 score points before the fix and are byte-identical after it. Two product defects surfaced with it: there was no seed plumbing anywhere (&lt;code&gt;LLMSettings.seed&lt;/code&gt; is new and applied by the Ollama provider; the default stays &lt;code&gt;None&lt;/code&gt;, so nothing changes unless you pin it), and &lt;code&gt;thinking_for_extraction&lt;/code&gt; was read by the schema extractor but never passed by the path every &lt;code&gt;chaoscypher load&lt;/code&gt; takes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In plain English:&lt;/strong&gt; if you compared local models on an earlier release, compare them again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preset chat defaults that can actually call tools
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;phi4:14b&lt;/code&gt; does not advertise tool calling in its Ollama manifest, and the product chat loop always passes the graph tools — so anyone on the 16 or 20 GB preset failed on the first turn with &lt;code&gt;ToolCallingNotSupportedError&lt;/code&gt;. Those tiers now ship &lt;code&gt;qwen3.5:9b&lt;/code&gt;, which scores 57/80 on the Book One chat board and is the strongest model under 14B on it. The 24 GB preset moves to &lt;code&gt;qwen3.8:27b&lt;/code&gt; (62/80) and 32/48 GB to &lt;code&gt;qwen3.6:35b-a3b&lt;/code&gt; (63/80) — the top scorers that fit each tier with 4 GB of context headroom. The previous default, &lt;code&gt;qwen3:30b&lt;/code&gt;, had never been benchmarked; the board measured the instruct variant.&lt;/p&gt;

&lt;p&gt;An existing install keeps whatever its &lt;code&gt;settings.yaml&lt;/code&gt; already names. Both model pickers in the app are now generated from the leaderboard data, and a model that cannot call tools, or whose chat run timed out, lands in a "Not recommended" group.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other fixes worth knowing about
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Exported packages carried zero node templates.&lt;/strong&gt; &lt;code&gt;_templates()&lt;/code&gt; dropped every &lt;code&gt;is_system&lt;/code&gt; template, and extraction files entities under system templates — so every mounted, loaded or composed entity arrived untyped.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A CLI-built package silently lost every citation&lt;/strong&gt; across a load or mount: &lt;code&gt;package export&lt;/code&gt; hard-coded &lt;code&gt;include_sources=False&lt;/code&gt; on the false premise that the CLI has no sources repository.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ollama ignored &lt;code&gt;llm_request_timeout&lt;/code&gt;.&lt;/strong&gt; A model that produced zero bytes sat 75 minutes at 100% GPU. The timeout is now the httpx read timeout, so silence fails on both the streaming and non-streaming paths while a progressing generation never trips it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The &lt;code&gt;\|&lt;/code&gt; escape the extraction prompt documents never worked&lt;/strong&gt; — the parser split on every &lt;code&gt;|&lt;/code&gt; and unescaped afterwards, so an entity named &lt;code&gt;Rostov \| Bolkonsky &amp;amp; Sons&lt;/code&gt; produced one field too many and the whole line was dropped.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Entity lines survived neither a missing nor a doubled &lt;code&gt;aliases&lt;/code&gt; field&lt;/strong&gt; — 740 + 18 such lines across the saved probe runs. &lt;code&gt;parse_entity_line&lt;/code&gt; now anchors on the confidence and sentence-reference pair instead of counting fields from the left.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;python -m chaoscypher_cortex.main start&lt;/code&gt; built the app and exited 0&lt;/strong&gt; — no &lt;code&gt;if __name__ == "__main__"&lt;/code&gt; guard, and that is the exact command both &lt;code&gt;chaoscypher serve&lt;/code&gt; and &lt;code&gt;compose up&lt;/code&gt; run.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Upgrading
&lt;/h2&gt;

&lt;p&gt;One migration, &lt;code&gt;0007_chunk_media_timestamps&lt;/code&gt;, is additive only — two nullable &lt;code&gt;REAL&lt;/code&gt; columns on &lt;code&gt;document_chunks&lt;/code&gt;, classified &lt;code&gt;safe_auto&lt;/code&gt;, so it applies on startup with automatic migrations enabled. There are no breaking changes to the product API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull ghcr.io/chaoscypherinc/chaoscypher:0.5.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, if you run the Python packages directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-U&lt;/span&gt; chaoscypher-core chaoscypher-cortex chaoscypher-neuron chaoscypher-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Starting fresh:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; chaoscypher &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 80:80 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 443:443 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; chaoscypher-data:/data &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--add-host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;host.docker.internal:host-gateway &lt;span class="se"&gt;\&lt;/span&gt;
  ghcr.io/chaoscypherinc/chaoscypher:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Three things to read before you upgrade
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Two defaults flipped toward carrying more data.&lt;/strong&gt; &lt;code&gt;chaoscypher package export&lt;/code&gt; and &lt;code&gt;graph package load&lt;/code&gt; now include sources, chunks and citations unless you pass &lt;code&gt;--no-sources&lt;/code&gt;. Exported packages are correspondingly larger, and loading one costs an inline search-indexing pass. This is the fix for silently losing every citation across a package round-trip, but it does change the size and timing of both commands.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Packages exported before this release carry no node templates&lt;/strong&gt;, and if they were built by the CLI, no sources. Re-export them to get typed entities and citations on the far side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The MCP benchmark tool payloads changed shape late in this release.&lt;/strong&gt; The benchmark tools are new in v0.5.0, so nothing released is broken — but if you built a client against &lt;code&gt;main&lt;/code&gt; while the extraction-probe track was landing, re-read the payloads. &lt;code&gt;get_benchmark_task&lt;/code&gt; now returns &lt;code&gt;task_id&lt;/code&gt;; &lt;code&gt;probe_id&lt;/code&gt; is a deprecated alias kept for one release. &lt;code&gt;submit_benchmark_output&lt;/code&gt; no longer returns a verdict — nothing is scored until &lt;code&gt;finish_benchmark&lt;/code&gt;. And each stage is answered exactly once: re-submitting is refused with &lt;code&gt;TASK_FINAL&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;If you keep knowledge packages, re-export them and try &lt;code&gt;chaoscypher mount&lt;/code&gt; against an MCP host — that is the shortest path from a built graph to an assistant that can query it.&lt;/li&gt;
&lt;li&gt;If you picked a local model from the app's dropdown before this release, check it against &lt;code&gt;/leaderboard&lt;/code&gt;. The truncation and determinism fixes above changed what the numbers mean.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Full details in the &lt;a href="https://chaoscypher.com/docs/about/changelog" rel="noopener noreferrer"&gt;changelog&lt;/a&gt;. Chaos Cypher is AGPL-3.0 and local-first: the graph, the chat, and the import and export paths all run on your own machine. Repo: &lt;a href="https://github.com/chaoscypherinc/chaoscypher" rel="noopener noreferrer"&gt;https://github.com/chaoscypherinc/chaoscypher&lt;/a&gt;&lt;/p&gt;

</description>
      <category>selfhosted</category>
      <category>opensource</category>
      <category>ai</category>
      <category>python</category>
    </item>
    <item>
      <title>Citation Click-Through: How Chaos Cypher Traces Every Answer to Its Source</title>
      <dc:creator>Denis MacPherson</dc:creator>
      <pubDate>Fri, 25 Sep 2026 14:51:36 +0000</pubDate>
      <link>https://dev.to/chaoscypherinc/citation-click-through-how-chaos-cypher-traces-every-answer-to-its-source-404i</link>
      <guid>https://dev.to/chaoscypherinc/citation-click-through-how-chaos-cypher-traces-every-answer-to-its-source-404i</guid>
      <description>&lt;p&gt;Most AI chat answers ask you to take them on faith. There's no way to tell which sentence came from your documents and which one the model filled in from its own training. Chaos Cypher takes a different position: every claim in a chat answer that's grounded in your knowledge graph carries a citation, and every citation is a link you can click.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem With Black-Box Answers
&lt;/h2&gt;

&lt;p&gt;Ask a chatbot a question about your own documents and you get an answer that reads well and might be completely right -- or quietly wrong in a way you'd only catch by re-reading the source yourself. The confidence of the answer tells you nothing about how well-grounded it is. That gap between "sounds right" and "is right" is the core trust problem with RAG-backed chat, and it only gets worse as your knowledge graph grows past what you can hold in your head.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Click-Through Looks Like
&lt;/h2&gt;

&lt;p&gt;Ask Chaos Cypher a question in &lt;strong&gt;Chat&lt;/strong&gt; and the streamed answer arrives with small inline markers next to the sentences it pulled from your sources -- a document icon for text, a thumbnail for an image chunk. Hover one and a tooltip shows the source it came from, the page number, and a validation badge: &lt;strong&gt;Verified&lt;/strong&gt; when the cited sentence was actually found in that chunk, &lt;strong&gt;Invalid&lt;/strong&gt; when it wasn't.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fax02jopz0ihmszz4b587.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fax02jopz0ihmszz4b587.png" alt="Chat page with an AI answer ranking the top entities by PageRank, with entity chips" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the marker and you land on that source's detail page with the exact chunk highlighted -- not "somewhere in this document," the specific paragraph the sentence was grounded in. Answers that pulled from a scanned page or image work the same way: the marker is a thumbnail, and clicking it opens the full image.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F58w00hqzu0lp1cdoj8iw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F58w00hqzu0lp1cdoj8iw.png" alt="Source overview for war_and_peace.txt: entity and relationship counts, pipeline flow, entity distribution" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters More Than a Nice-to-Have
&lt;/h2&gt;

&lt;p&gt;This closes the loop that most RAG chat interfaces leave open. Instead of trusting a generated answer wholesale, you can verify any individual sentence in a click -- confirm it against the source, catch a citation that doesn't hold up, or just read more context around the fact you asked about. It's the same underlying link between graph and source text that powers node detail pages in the graph view; chat citations are that mechanism surfaced at the point where you're actually reading an answer.&lt;/p&gt;

&lt;p&gt;In plain English: if Chaos Cypher tells you something, you can check it -- one click, straight to the sentence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;Ask a question about a document you've already imported and look for the citation markers in the answer. If you haven't imported anything yet, the &lt;a href="https://chaoscypher.com/blog/graphrag-ollama-10-minutes" rel="noopener noreferrer"&gt;GraphRAG with Ollama in 10 minutes&lt;/a&gt; quickstart gets you from zero to a cited answer in about ten minutes, fully local.&lt;/p&gt;

</description>
      <category>graphrag</category>
      <category>ai</category>
      <category>rag</category>
      <category>selfhosted</category>
    </item>
    <item>
      <title>GraphRAG Teardown: What the Graph Actually Adds to Naive RAG</title>
      <dc:creator>Denis MacPherson</dc:creator>
      <pubDate>Fri, 25 Sep 2026 14:51:30 +0000</pubDate>
      <link>https://dev.to/chaoscypherinc/graphrag-teardown-what-the-graph-actually-adds-to-naive-rag-883</link>
      <guid>https://dev.to/chaoscypherinc/graphrag-teardown-what-the-graph-actually-adds-to-naive-rag-883</guid>
      <description>&lt;p&gt;Take three well-known AI agent papers -- chain-of-thought prompting, ReAct, and Reflexion -- drop them into a knowledge base, and ask a question that spans all three: &lt;em&gt;"How does Reflexion's self-reflection loop relate to chain-of-thought prompting?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Naive vector RAG will hand you a competent paragraph about chain-of-thought. The connection you actually asked about -- that ReAct interleaved chain-of-thought-style reasoning traces with actions, and that Reflexion layered verbal self-reflection on top of ReAct-style agents, with two authors carrying across both papers -- is spread across three documents and stated explicitly in none of them.&lt;/p&gt;

&lt;p&gt;This post is a teardown of both retrieval paths over exactly that corpus. Not a leaderboard: a walk through what each one mechanically does, where the vector path stalls, and where the graph earns its keep.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Post Is (And Isn't)
&lt;/h2&gt;

&lt;p&gt;This is a &lt;strong&gt;mechanism teardown&lt;/strong&gt;, not a benchmark. You will not find a table here claiming Chaos Cypher scores X% against a baseline's Y%. Retrieval quality depends enormously on corpus, chunking, embedding model, and question shape, and a number produced by us on a corpus chosen by us would tell you very little.&lt;/p&gt;

&lt;p&gt;What you &lt;em&gt;will&lt;/em&gt; find: the exact retrieval steps both paths take, the real relationships in this three-paper corpus that separate them, and the retrieval statistics Chaos Cypher emits on every query so you can run the same comparison on your own documents and read the receipts yourself.&lt;/p&gt;

&lt;p&gt;It's worth stating the honest version of the research picture up front. The most thorough public analysis of this question -- &lt;a href="https://arxiv.org/abs/2506.05690" rel="noopener noreferrer"&gt;&lt;em&gt;When to use Graphs in RAG: A Comprehensive Analysis for Graph Retrieval-Augmented Generation&lt;/em&gt;&lt;/a&gt; (the GraphRAG-Bench paper, ICLR 2026) -- exists precisely because graph retrieval &lt;strong&gt;frequently underperforms vanilla RAG&lt;/strong&gt; on many real-world tasks. Its contribution is mapping &lt;em&gt;when&lt;/em&gt; graph structure pays off: the benefit concentrates in complex, multi-hop reasoning, and largely evaporates on simple fact retrieval, where a well-tuned vector index is already the right tool.&lt;/p&gt;

&lt;p&gt;That matches our own position, and it's why Chaos Cypher fuses the two rather than replacing one with the other. The graph is not a better vector index. It answers a different question.&lt;/p&gt;

&lt;p&gt;In plain English: graphs help with questions that require connecting things, not with questions that require finding one thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Corpus and the Question
&lt;/h2&gt;

&lt;p&gt;Three papers, all publicly available:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Paper&lt;/th&gt;
&lt;th&gt;arXiv&lt;/th&gt;
&lt;th&gt;Venue&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Chain-of-Thought Prompting Elicits Reasoning in Large Language Models (Wei et al.)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://arxiv.org/abs/2201.11903" rel="noopener noreferrer"&gt;2201.11903&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;NeurIPS 2022&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ReAct: Synergizing Reasoning and Acting in Language Models (Yao et al.)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://arxiv.org/abs/2210.03629" rel="noopener noreferrer"&gt;2210.03629&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;ICLR 2023&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reflexion: Language Agents with Verbal Reinforcement Learning (Shinn et al.)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://arxiv.org/abs/2303.11366" rel="noopener noreferrer"&gt;2303.11366&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;NeurIPS 2023&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The relationships between them are real and checkable. ReAct builds directly on chain-of-thought reasoning, interleaving reasoning traces with actions rather than producing reasoning alone. Reflexion builds on that line of work, adding a verbal self-reflection buffer that feeds an agent's own failures back into its next attempt. And the author lists overlap: Shunyu Yao and Karthik Narasimhan appear on both ReAct and Reflexion.&lt;/p&gt;

&lt;p&gt;Now the question: &lt;strong&gt;"How does Reflexion's self-reflection loop relate to chain-of-thought prompting?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Note what makes this hard. No single chunk in any of the three papers contains the answer. The chain runs chain-of-thought → ReAct → Reflexion, and each link lives in a different document.&lt;/p&gt;

&lt;h2&gt;
  
  
  Round 1: What Naive Vector RAG Does
&lt;/h2&gt;

&lt;p&gt;The naive path is three steps, and it is genuinely good at what it does:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Embed the question&lt;/strong&gt; into a vector.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compare&lt;/strong&gt; that vector against every chunk embedding by cosine similarity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Return&lt;/strong&gt; the top &lt;em&gt;k&lt;/em&gt; closest chunks.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ask our question and the retrieved chunks will be dominated by passages that &lt;em&gt;talk like the question&lt;/em&gt;. The phrase "self-reflection" pulls hard toward Reflexion's abstract and method section. "Chain-of-thought prompting" pulls toward the Wei et al. paper. You get strong chunks from both ends of the chain.&lt;/p&gt;

&lt;p&gt;What you almost certainly do not get is the middle. ReAct is the load-bearing link -- it's what connects reasoning traces to acting agents -- but a ReAct passage doesn't necessarily &lt;em&gt;sound&lt;/em&gt; like a question about Reflexion and chain-of-thought. Its vocabulary is its own: interleaving, action space, observation. Lexical and semantic similarity to the query is mediocre, so it ranks below chunks that merely restate the question's own terms.&lt;/p&gt;

&lt;p&gt;The failure mode is worth naming precisely: &lt;strong&gt;vector search has no representation of the fact that these three documents are related.&lt;/strong&gt; It sees text, scores text, returns text. Two chunks that describe the same idea in different vocabulary are far apart; two chunks that share vocabulary but no actual relationship are close together. There is no edge between ReAct and Reflexion in a vector index, because a vector index has no edges at all.&lt;/p&gt;

&lt;p&gt;And it fails quietly. There's no error, no "I found the endpoints but not the path." You get a fluent answer built from the two ends of a three-link chain, and it reads exactly as confident as a complete one would.&lt;/p&gt;

&lt;p&gt;In plain English: vector search finds text that sounds like your question, which is not the same as text that answers it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Round 2: What GraphRAG Does
&lt;/h2&gt;

&lt;p&gt;Chaos Cypher's GraphRAG pipeline runs seven steps. The graph half and the vector half both run, and the last step merges them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1 — Embed the query.&lt;/strong&gt; Identical starting point to the naive path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — Match seed entities.&lt;/strong&gt; Before touching document chunks, the query vector is compared against &lt;em&gt;entity&lt;/em&gt; embeddings in the knowledge graph. Entities clearing a cosine-similarity floor (&lt;code&gt;seed_similarity_threshold&lt;/code&gt;, default &lt;code&gt;0.3&lt;/code&gt;) become seeds, up to a &lt;code&gt;seed_limit&lt;/code&gt;. For our question, expect seeds like &lt;code&gt;Reflexion&lt;/code&gt;, &lt;code&gt;self-reflection&lt;/code&gt;, and &lt;code&gt;chain-of-thought prompting&lt;/code&gt; -- the anchor points, not the answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — Personalized PageRank.&lt;/strong&gt; Standard PageRank finds globally important nodes. &lt;em&gt;Personalized&lt;/em&gt; PageRank restarts its random walk at your seeds, turning a global importance score into a query-specific relevance score. Chaos Cypher runs rustworkx's compiled power iteration with a damping factor of &lt;code&gt;0.85&lt;/code&gt; (&lt;code&gt;ppr_damping&lt;/code&gt;), in-process -- no external graph database. Seed weights come from the Step 2 similarity scores, so the walk is biased toward the entities that actually matter for &lt;em&gt;this&lt;/em&gt; question.&lt;/p&gt;

&lt;p&gt;This is where ReAct surfaces. It was never a strong seed, because it isn't semantically close to the query. It scores well because it sits structurally between two entities that &lt;em&gt;are&lt;/em&gt; -- one or two hops from both &lt;code&gt;Reflexion&lt;/code&gt; and &lt;code&gt;chain-of-thought&lt;/code&gt;. The top &lt;code&gt;ppr_top_k&lt;/code&gt; entities (default &lt;code&gt;20&lt;/code&gt;) carry forward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4 — Assemble graph context.&lt;/strong&gt; Seeds, discovered entities, and the relationship triples connecting them are collected into a structured context (capped by &lt;code&gt;max_triples&lt;/code&gt;, default &lt;code&gt;200&lt;/code&gt;) and passed to the model alongside the text. The model gets the &lt;em&gt;map&lt;/em&gt;, not just the territory. On this corpus, extraction should yield edges along the lines of &lt;code&gt;Reflexion --builds_on--&amp;gt; ReAct&lt;/code&gt;, &lt;code&gt;ReAct --extends--&amp;gt; chain-of-thought prompting&lt;/code&gt;, &lt;code&gt;Shunyu Yao --authored--&amp;gt; ReAct&lt;/code&gt;, &lt;code&gt;Shunyu Yao --authored--&amp;gt; Reflexion&lt;/code&gt; -- the exact labels depend on what extraction pulled from your documents, which is the subject of the limitations section below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5 — Retrieve provenance chunks.&lt;/strong&gt; The first of two retrieval paths. For every entity the graph surfaced, Chaos Cypher looks up which document chunks that entity was originally extracted from. This is what makes the graph auditable rather than decorative: the ReAct passage arrives with the evidence for why it was retrieved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 6 — Retrieve vector chunks.&lt;/strong&gt; The second path is ordinary hybrid search -- semantic plus keyword -- across all chunks. This is the naive path, running unchanged, inside the graph pipeline. It catches relevant passages that never produced graph entities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 7 — Merge and rank.&lt;/strong&gt; Two independently ranked lists, two incompatible scoring scales. Chaos Cypher merges them with Reciprocal Rank Fusion (&lt;a href="https://dl.acm.org/doi/10.1145/1571941.1572114" rel="noopener noreferrer"&gt;Cormack, Clarke &amp;amp; Buettcher, 2009&lt;/a&gt;), which discards raw scores and uses only rank position: each chunk scores the sum of &lt;code&gt;1 / (k + rank)&lt;/code&gt; across every list it appears in, with &lt;code&gt;k = 60&lt;/code&gt; per the original paper.&lt;/p&gt;

&lt;p&gt;The property that matters: a chunk appearing in &lt;em&gt;both&lt;/em&gt; lists collects from both. A passage ranked 5th by provenance and 8th by vector search will often outrank one that's 1st by vector search and absent from provenance. Evidence confirmed by two independent signals beats evidence confirmed by one.&lt;/p&gt;

&lt;p&gt;In plain English: the graph finds the path between the things you asked about, the vector index finds text that sounds like your question, and fusion trusts what both agree on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyeha9i1bezyqe2x3nq8e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyeha9i1bezyqe2x3nq8e.png" alt="Command palette searching for Pierre, listing entity results with connection counts" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading the Receipts
&lt;/h2&gt;

&lt;p&gt;The part that makes this a teardown rather than a story: every GraphRAG query returns a &lt;code&gt;retrieval_stats&lt;/code&gt; block alongside its results. Six fields, all observable:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;What it tells you&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Which pipeline actually ran (see below)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;seed_entities_found&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;How many graph entities anchored the walk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ppr_entities_explored&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;How many entities PageRank scored&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;provenance_chunks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Chunks retrieved via graph provenance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vector_chunks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Chunks retrieved via hybrid search&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deduplicated&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Chunks the two paths agreed on and were merged&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;mode&lt;/code&gt; is the honest one. The pipeline degrades rather than failing, and it tells you which rung it landed on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;full_graphrag&lt;/code&gt;&lt;/strong&gt; -- seeds found, PageRank succeeded. Graph context, provenance chunks, vector chunks, fusion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;vector_only&lt;/code&gt;&lt;/strong&gt; -- embeddings worked, but no graph seeds matched (or PageRank produced nothing). Hybrid search, no graph context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;keyword_only&lt;/code&gt;&lt;/strong&gt; -- no embeddings available. SQLite full-text keyword search.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the number to watch when you evaluate GraphRAG on your own corpus. A query returning &lt;code&gt;vector_only&lt;/code&gt; did not get any graph benefit, whatever the answer looked like -- and if most of your queries come back &lt;code&gt;vector_only&lt;/code&gt;, your graph isn't the problem, your &lt;em&gt;extraction&lt;/em&gt; is. Likewise, a &lt;code&gt;deduplicated&lt;/code&gt; count near zero means the two paths found completely disjoint evidence, which is usually a sign the query was single-hop and the graph half was along for the ride.&lt;/p&gt;

&lt;p&gt;In plain English: don't take our word for whether the graph fired -- the response tells you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the Graph Doesn't Help
&lt;/h2&gt;

&lt;p&gt;Being straight about this is more useful than a win column.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Single-hop fact lookup.&lt;/strong&gt; "What damping factor does PageRank use?" needs one chunk. The graph adds latency and nothing else. This is the GraphRAG-Bench finding in miniature, and it describes a lot of real queries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Small or unstructured corpora.&lt;/strong&gt; Three papers is enough to demonstrate a citation chain; a single document with no cross-references gives PageRank nothing to walk. Below a few interlinked documents, expect &lt;code&gt;vector_only&lt;/code&gt; most of the time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Extraction quality is the ceiling.&lt;/strong&gt; The graph is only as good as the entities and relationships pulled out of your text during import. Miss the &lt;code&gt;Reflexion --builds_on--&amp;gt; ReAct&lt;/code&gt; edge at extraction time and no amount of clever retrieval invents it. When GraphRAG disappoints, the fix is usually upstream in extraction, not in retrieval.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Very large graphs skip PageRank entirely.&lt;/strong&gt; There's a &lt;code&gt;max_graph_nodes&lt;/code&gt; ceiling (default &lt;code&gt;50,000&lt;/code&gt;) on how much graph gets loaded for a walk. Past it, the pipeline skips PPR rather than stalling on a query.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The seed threshold is a real gate.&lt;/strong&gt; Nothing clears &lt;code&gt;0.3&lt;/code&gt; cosine similarity, nothing seeds, and you're in &lt;code&gt;vector_only&lt;/code&gt; no matter how rich the graph is. Vocabulary mismatch between your question and your extracted entities shows up here first.&lt;/p&gt;

&lt;p&gt;In plain English: the graph is a specialist, and a system that pretends otherwise is selling something.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run It Yourself
&lt;/h2&gt;

&lt;p&gt;Both halves are separately observable in a running instance, which is the point:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The naive path alone:&lt;/strong&gt; the search bar exposes &lt;code&gt;keyword&lt;/code&gt;, &lt;code&gt;semantic&lt;/code&gt;, and &lt;code&gt;hybrid&lt;/code&gt; modes directly. &lt;code&gt;semantic&lt;/code&gt; is textbook vector RAG -- use it as your baseline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The full path:&lt;/strong&gt; ask the same question in &lt;strong&gt;Chat&lt;/strong&gt;, which retrieves through GraphRAG, and compare what gets cited. Every citation clicks through to the exact source chunk, so you can check whether the middle of the chain actually showed up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Programmatically:&lt;/strong&gt; the &lt;code&gt;graphrag_search&lt;/code&gt; MCP tool runs the whole pipeline and returns &lt;code&gt;graph_context&lt;/code&gt;, &lt;code&gt;chunks&lt;/code&gt;, and &lt;code&gt;retrieval_stats&lt;/code&gt; as structured data -- the cleanest way to diff retrieval paths across a question set.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The three papers above make a good starter corpus precisely because you already know the right answer, which makes a wrong one easy to spot.&lt;/p&gt;

&lt;p&gt;If you want the conceptual version of why multi-hop breaks vector search, that's &lt;a href="https://chaoscypher.com/blog/graphrag-enhanced-search" rel="noopener noreferrer"&gt;Why Your RAG Chat is Missing Half the Answers&lt;/a&gt;. If you don't have an instance running yet, &lt;a href="https://chaoscypher.com/blog/graphrag-ollama-10-minutes" rel="noopener noreferrer"&gt;GraphRAG with Ollama in 10 minutes&lt;/a&gt; gets you from zero to a cited answer, fully local.&lt;/p&gt;

</description>
      <category>graphrag</category>
      <category>rag</category>
      <category>ai</category>
      <category>python</category>
    </item>
    <item>
      <title>The CCX Open Format: A Portable Spec for Knowledge Graphs</title>
      <dc:creator>Denis MacPherson</dc:creator>
      <pubDate>Fri, 25 Sep 2026 14:50:48 +0000</pubDate>
      <link>https://dev.to/chaoscypherinc/the-ccx-open-format-a-portable-spec-for-knowledge-graphs-3p61</link>
      <guid>https://dev.to/chaoscypherinc/the-ccx-open-format-a-portable-spec-for-knowledge-graphs-3p61</guid>
      <description>&lt;p&gt;A knowledge graph you cannot take with you is not really yours. You can love the tool that built it, and still want the guarantee that the graph outlives the tool -- readable in five years, on a machine that has never heard of the software that produced it.&lt;/p&gt;

&lt;p&gt;That guarantee only exists if the file format is written down.&lt;/p&gt;

&lt;p&gt;So we wrote it down. CCX -- Chaos Cypher eXchange, the &lt;code&gt;.ccx&lt;/code&gt; file Chaos Cypher already exports -- now has a &lt;strong&gt;&lt;a href="https://chaoscypher.com/docs/reference/ccx-format" rel="noopener noreferrer"&gt;published draft specification&lt;/a&gt;&lt;/strong&gt; and a &lt;strong&gt;standalone reader library&lt;/strong&gt; you can install and use with no Chaos Cypher anywhere in the picture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;ccx-format        &lt;span class="c"&gt;# import name: ccx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reader is Apache-2.0 and lives in its own repository (&lt;a href="https://github.com/chaoscypherinc/ccx" rel="noopener noreferrer"&gt;chaoscypherinc/ccx&lt;/a&gt;, &lt;a href="https://pypi.org/project/ccx-format/" rel="noopener noreferrer"&gt;PyPI&lt;/a&gt;) -- deliberately more permissive than Chaos Cypher itself, because a format nobody can implement against is a format nobody adopts.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Changes For You
&lt;/h2&gt;

&lt;p&gt;If you use Chaos Cypher, nothing you do changes today. Export still works the way it did; the file you get is the same file.&lt;/p&gt;

&lt;p&gt;What changed is what that file &lt;em&gt;means&lt;/em&gt;. Before, &lt;code&gt;.ccx&lt;/code&gt; was an implementation detail -- whatever our exporter happened to write, readable by whatever our importer happened to accept. Now it is a documented artifact with a schema, a validator, and a second piece of software that reads it. Your export is no longer a bet on us staying in business.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In plain English:&lt;/strong&gt; your &lt;code&gt;.ccx&lt;/code&gt; file is now a document with a spec behind it, not just a save file.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Actually In a Package
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;.ccx&lt;/code&gt; file is a ZIP archive with a fixed layout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mimetype                              # first entry, uncompressed
manifest.json                         # metadata + member inventory
context.jsonld                        # the JSON-LD context, carried inline
knowledge.jsonld                      # the default graph
graphs/&amp;lt;namespace&amp;gt;.&amp;lt;name&amp;gt;.jsonld      # additional named graphs
sources.jsonl                         # source and chunk records
shapes.ttl                            # optional SHACL shapes
assets/sha256/&amp;lt;hex&amp;gt;                   # content-addressed assets
signatures/manifest.sig               # optional detached signature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four decisions in there are worth pulling out, because they are the ones that make the file trustworthy rather than merely readable:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The archive is deterministic.&lt;/strong&gt; Entry timestamps are pinned to &lt;code&gt;1980-01-01&lt;/code&gt; and file modes to &lt;code&gt;0644&lt;/code&gt;, so the same inputs produce a byte-identical archive. You can checksum an export and mean something by it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The media type is in the first bytes.&lt;/strong&gt; The first entry is always an uncompressed file named &lt;code&gt;mimetype&lt;/code&gt; containing &lt;code&gt;application/vnd.ccx+zip&lt;/code&gt; -- the same trick EPUB uses. A file identifies itself without being unpacked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Everything is checksummed twice.&lt;/strong&gt; Every graph and asset in the manifest carries both a SHA-256 and a SHA-512 digest, and integrity verification requires &lt;em&gt;both&lt;/em&gt; to match. Readers also enforce hard limits before inflating anything -- at most 100,000 entries, 512 MiB per entry, 2 GiB total -- so a malicious package cannot zip-bomb a consumer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Remote contexts are forbidden.&lt;/strong&gt; A conformant reader rejects any &lt;code&gt;http(s)://&lt;/code&gt; JSON-LD &lt;code&gt;@context&lt;/code&gt; reference anywhere in the package, and loads graphs with network access disabled. This is the rule we are most opinionated about: a package must be fully interpretable offline, forever, with no live server anywhere in the chain. It is a security property, not a style preference.&lt;/p&gt;

&lt;p&gt;The knowledge itself lives in &lt;code&gt;knowledge.jsonld&lt;/code&gt; as JSON-LD nodes, with relationships either attached directly to their subject or reified as standalone &lt;code&gt;Relationship&lt;/code&gt; resources when they carry properties of their own. Sources and retrieval chunks live in &lt;code&gt;sources.jsonl&lt;/code&gt;, one JSON object per line, with chunks pointing back into retained full text by character offset -- which is what lets a citation survive the trip.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In plain English:&lt;/strong&gt; it's a ZIP with a strict layout, checksums on everything, and a hard rule that it never needs the internet to be read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading a Package Without Chaos Cypher
&lt;/h2&gt;

&lt;p&gt;This is the part that makes "open" mean something. Install the reader and open any &lt;code&gt;.ccx&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ccx&lt;/span&gt;

&lt;span class="n"&gt;pkg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ccx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open_package&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-graph.ccx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pkg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;classes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# e.g. True ('core', 'sources', 'shapes')
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is a CLI too -- &lt;code&gt;ccx inspect&lt;/code&gt;, &lt;code&gt;ccx validate&lt;/code&gt;, and &lt;code&gt;ccx pack&lt;/code&gt; -- so you can check a package without writing any code at all.&lt;/p&gt;

&lt;p&gt;Note what &lt;code&gt;validate()&lt;/code&gt; hands back: not a boolean, but a report with errors, warnings, and a set of &lt;strong&gt;conformance classes&lt;/strong&gt;. Those classes are the format's honesty mechanism.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;Granted when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;core&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The container, manifest, context, and graphs all check out.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sources&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;sources.jsonl&lt;/code&gt; parses and every chunk offset is in range against its text asset.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;shapes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;shapes.ttl&lt;/code&gt; is present, parses as Turtle, and declares at least one SHACL shape.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;embeddings&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The manifest declares embeddings and every included sidecar is present.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;signed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The manifest declares signatures and at least one verifies offline.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The classes above &lt;code&gt;core&lt;/code&gt; are &lt;strong&gt;independent capabilities, not a stack&lt;/strong&gt;, and absence is never an error -- a package is judged only on what it claims by actually shipping the relevant artifacts. A package with no embeddings is not a failing package; it simply is not granted the &lt;code&gt;embeddings&lt;/code&gt; class. A package that claims embeddings and ships them broken does not fail validation either: it collects warnings and the class is withheld. So a consumer can ask "does this package have verifiable sources?" and get a straight answer, instead of parsing it and finding out.&lt;/p&gt;

&lt;p&gt;A typical full Chaos Cypher export validates as &lt;code&gt;core&lt;/code&gt;, &lt;code&gt;sources&lt;/code&gt;, &lt;code&gt;shapes&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In plain English:&lt;/strong&gt; any Python program can open a &lt;code&gt;.ccx&lt;/code&gt; file, and the validator tells it exactly which parts of the package it can trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Producing One From Chaos Cypher
&lt;/h2&gt;

&lt;p&gt;Nothing new here, but for completeness -- the graphical path is &lt;strong&gt;Settings → General → Import &amp;amp; Export&lt;/strong&gt;: pick which components to include, click Export, and the browser downloads the &lt;code&gt;.ccx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9tjk220eylo5g0zdhsbd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9tjk220eylo5g0zdhsbd.png" alt="Settings → General with the Import &amp;amp; Export row and its Export and Import buttons" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For a single document's worth of knowledge rather than the whole graph, use &lt;strong&gt;Export Source&lt;/strong&gt; in that source's action menu on the Sources page.&lt;/p&gt;

&lt;p&gt;CLI users get the same thing with more control:&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;# Whole graph, default components&lt;/span&gt;
chaoscypher graph package &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; my-research.ccx &lt;span class="nt"&gt;--title&lt;/span&gt; &lt;span class="s2"&gt;"My Research"&lt;/span&gt;

&lt;span class="c"&gt;# Templates and lenses only, no entities -- a shareable schema&lt;/span&gt;
chaoscypher graph package &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nt"&gt;--no-knowledge&lt;/span&gt; &lt;span class="nt"&gt;--no-workflows&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; schema-only.ccx

&lt;span class="c"&gt;# Include embedding vectors (off by default -- only useful for same-model migration)&lt;/span&gt;
chaoscypher graph package &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nt"&gt;--embeddings&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; full.ccx

&lt;span class="c"&gt;# Load one back&lt;/span&gt;
chaoscypher graph package load my-research.ccx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Imports are an &lt;strong&gt;upsert by IRI&lt;/strong&gt;, not a copy: Chaos Cypher mints locally scoped identifiers and preserves foreign ones on re-export, so loading a package twice does not duplicate its contents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In plain English:&lt;/strong&gt; two clicks in Settings, or one command in the terminal, and you have a package.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Honest Limits
&lt;/h2&gt;

&lt;p&gt;This is draft 1 of a 3.0 spec, and there is a difference between "documented" and "finished." The things we would want to know if we were reading someone else's format announcement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;We are not calling it a standard.&lt;/strong&gt; There is one reference implementation, written by us. A format with a single implementation is a documented format, and that is all we are claiming. That word is available to us when a second, independent implementation exists -- not before.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The spec is revised in place&lt;/strong&gt; while draft status holds. The revision marker at the top of the spec page identifies the exact text a tool was written against; check it rather than assuming.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workflows do not round-trip today.&lt;/strong&gt; The &lt;code&gt;chaoscypher.workflows&lt;/code&gt; graph member currently carries workflow &lt;em&gt;trigger&lt;/em&gt; rows only, not full workflow definitions, and our own importer skips that member with a warning. It is in the package and it is not yet useful; we would rather say so than let you discover it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signatures are specified but not emitted.&lt;/strong&gt; The format defines detached &lt;code&gt;ed25519&lt;/code&gt; and &lt;code&gt;sigstore&lt;/code&gt; signatures with offline, fail-closed verification, and reserves post-quantum format names. Chaos Cypher does not produce signed packages yet, so the &lt;code&gt;signed&lt;/code&gt; class is currently unreachable from our exports.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embeddings are opt-in and model-bound.&lt;/strong&gt; They ship as Parquet sidecars and are off by default, because a vector is only useful to someone running the same embedding model.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;In plain English:&lt;/strong&gt; the file format is written down and stable enough to build against; the parts that are not finished are named above rather than left for you to trip over.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Want From This
&lt;/h2&gt;

&lt;p&gt;The interesting question is not whether Chaos Cypher can read its own files. It is whether anything else can.&lt;/p&gt;

&lt;p&gt;So the specific thing that would make this real: &lt;strong&gt;one independent implementation.&lt;/strong&gt; A loader that pulls a &lt;code&gt;.ccx&lt;/code&gt; into LlamaIndex or LangChain. An exporter that writes one out of Obsidian. A validator in another language. Any of those, written by someone who is not us, is the difference between a documented format and a shared one.&lt;/p&gt;

&lt;p&gt;If you are building in this space, the &lt;a href="https://chaoscypher.com/docs/reference/ccx-format" rel="noopener noreferrer"&gt;spec page&lt;/a&gt; is the whole contract and the &lt;a href="https://github.com/chaoscypherinc/ccx" rel="noopener noreferrer"&gt;reader&lt;/a&gt; is the reference. Both are open, and issues and questions on either are genuinely welcome -- draft 1 is exactly the stage where the spec should be argued with.&lt;/p&gt;

&lt;p&gt;If you just want your own graph to be portable: it already is. That was the point.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Running Chaos Cypher fully locally with Ollama? See the &lt;a href="https://chaoscypher.com/blog/local-ai-knowledge-graph" rel="noopener noreferrer"&gt;local-first setup guide&lt;/a&gt; -- packages work identically there, and never touch a network.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>python</category>
      <category>graphrag</category>
      <category>selfhosted</category>
    </item>
    <item>
      <title>ChaosCypher v0.4.3: When a Clean Restart Lost More Work Than a Crash</title>
      <dc:creator>Denis MacPherson</dc:creator>
      <pubDate>Tue, 22 Sep 2026 20:35:42 +0000</pubDate>
      <link>https://dev.to/chaoscypherinc/chaoscypher-v043-when-a-clean-restart-lost-more-work-than-a-crash-1c2a</link>
      <guid>https://dev.to/chaoscypherinc/chaoscypher-v043-when-a-clean-restart-lost-more-work-than-a-crash-1c2a</guid>
      <description>&lt;p&gt;v0.4.3 adds nothing new. No new features, no breaking API changes, no schema migrations: 54 commits since v0.4.2, 23 of them changing shipped code. Most of them are in the queue and the extraction pipeline, and most of those share one trait. The work was lost, counted twice, or quietly downgraded, and nothing reported it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F02uolct3m935zqakcdhn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F02uolct3m935zqakcdhn.png" alt="Knowledge graph visualization showing extracted entities and relationships" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  A clean restart was worse than &lt;code&gt;kill -9&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Stopping a worker gracefully destroyed work that a hard kill would have recovered.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On shutdown, the drain's &lt;code&gt;CancelledError&lt;/code&gt; arm wrote terminal &lt;code&gt;status="cancelled"&lt;/code&gt; to the tasks it interrupted. That is exactly the status the requeue script and &lt;code&gt;retry_task&lt;/code&gt; refuse to resurrect. So the recovery contract ran backwards: a &lt;code&gt;SIGKILL&lt;/code&gt; left the task recoverable, and a polite restart finished it off.&lt;/p&gt;

&lt;p&gt;It was one of four queue-recovery defects in this release that each looked like a working system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Crash recovery spent two units of the retry budget per cycle.&lt;/strong&gt; &lt;code&gt;requeue_atomic.lua&lt;/code&gt; bumped &lt;code&gt;attempts&lt;/code&gt; on top of the worker's own claim-time increment, so a &lt;code&gt;max_tries=5&lt;/code&gt; task with &lt;code&gt;retry_on_crash=True&lt;/code&gt; got 3 dispatches, not 5, and then went terminal reporting "crashed after 5 attempts". The affected operations are the four crash-retryable ones with no owning resource: &lt;code&gt;execute_workflow&lt;/code&gt;, &lt;code&gt;execute_step&lt;/code&gt;, &lt;code&gt;export_graph&lt;/code&gt;, &lt;code&gt;export_by_sources&lt;/code&gt;. For those, the queue budget is the only recovery layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A lapsed heartbeat could never be refreshed.&lt;/strong&gt; &lt;code&gt;refresh_heartbeat&lt;/code&gt; issued an &lt;code&gt;EXPIRE&lt;/code&gt;, which is a silent no-op on a key that has already expired. A 31-second stall condemned a multi-hour task as abandoned, and the reconciler requeued it for duplicate execution. It is now a &lt;code&gt;SET&lt;/code&gt; with &lt;code&gt;EX&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A re-delivered chunk task could finish the job one chunk early.&lt;/strong&gt; The short-circuit that made re-running an extraction chunk safe only caught a &lt;code&gt;completed&lt;/code&gt; row. A row still in &lt;code&gt;running&lt;/code&gt; went straight through, so two deliveries produced two terminal writes and two job-counter bumps. Both transitions are now guarded updates that return a rowcount, and the handler bails out &lt;em&gt;before&lt;/em&gt; the billable LLM call when it loses the claim.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In plain English: if you restart workers, or your machine stalls for half a minute, queued work now comes back the number of times it was promised to, and only once.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Graph edges that explained themselves by thinking out loud
&lt;/h3&gt;

&lt;p&gt;Every relationship ChaosCypher extracts carries a justification: the evidence the model gave for linking two entities. You can inspect it, and it is part of why the graph is checkable rather than taken on trust. That only works if the justification is evidence.&lt;/p&gt;

&lt;p&gt;Some weren't. One &lt;code&gt;parent_of&lt;/code&gt; edge at confidence 0.9 explained itself with &lt;em&gt;"I will link 9 to 3 via &lt;code&gt;interacts_with&lt;/code&gt;"&lt;/em&gt;. That is the model deliberating, stored as if it were the reason the edge exists. Justifications that carry reasoning markers are now blanked, and the rest are trimmed to two sentences on a sentence or word boundary, so no fragment survives.&lt;/p&gt;

&lt;p&gt;In plain English: when you read why an edge exists, you now get a reason or nothing, not the model's scratchpad.&lt;/p&gt;

&lt;h3&gt;
  
  
  One MCP sandbox bypass, and a read-only mode that wasn't
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;MCP &lt;code&gt;add_document&lt;/code&gt; skipped its sandbox containment whenever &lt;code&gt;content&lt;/code&gt; was supplied.&lt;/strong&gt; The tool schema says that supplying content means the file is never loaded. Nothing implemented that: &lt;code&gt;file_path&lt;/code&gt; was read from disk on every path and reached the loader uncontained. A caller supplying both got an arbitrary-file read that reached &lt;code&gt;/data/credentials.json&lt;/code&gt;. The containment and dotfile guards are now unconditional, in the server and in the processor's mirror of it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;chaoscypher mcp&lt;/code&gt; honoured a read-only install only when you typed the flag.&lt;/strong&gt; The guard read the &lt;code&gt;--mode&lt;/code&gt; flag rather than the effective setting, so the documented Claude Desktop invocation against a &lt;code&gt;read&lt;/code&gt;-configured install still handed the external client the destructive &lt;code&gt;apply_upgrade&lt;/code&gt; tool. The guard now reads the configured mode, and an explicit &lt;code&gt;--mode write&lt;/code&gt; still overrides it.&lt;/p&gt;

&lt;p&gt;Also closed: the API-key selector and bearer-token throttle defects found in a post-merge sweep; &lt;code&gt;decode_session&lt;/code&gt; rejects non-ASCII input; &lt;code&gt;summarize&lt;/code&gt; gets the untrusted-document fence its five sibling handlers already had; and &lt;code&gt;CHAOSCYPHER_ALLOW_USER_PLUGINS=0&lt;/code&gt;, the documented Docker kill switch for user plugins, now actually reaches all five plugin-loading services. Before, no service declared it, so the loader always saw its default.&lt;/p&gt;

&lt;p&gt;In plain English: if you point an AI assistant at ChaosCypher over MCP, it can no longer read files outside the sandbox, and "read-only" means read-only however you launch it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Settings that silently did nothing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Every configured trigger filter matched everything.&lt;/strong&gt; &lt;code&gt;list_triggers&lt;/code&gt; deferred the &lt;code&gt;filters&lt;/code&gt; and &lt;code&gt;actions&lt;/code&gt; columns the dispatcher reads, so a trigger scoped to one source fired on every source. The &lt;code&gt;.ccx&lt;/code&gt; exporter and &lt;code&gt;GET /workflows/{id}/triggers&lt;/code&gt; were losing the same keys.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A rejected &lt;code&gt;workers.yaml&lt;/code&gt; value collapsed the Operations queue from 8 concurrent slots to 1&lt;/strong&gt;, because a bad value fell through to a boolean instead of the worker type's default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The commit-time orphan filter ignored the source's own settings&lt;/strong&gt; and always dropped orphans, even for minimal or unfiltered sources and rows with &lt;code&gt;protect_orphans=True&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The queue reset promised cancellation and delivered dead letters.&lt;/strong&gt; It now runs &lt;code&gt;cancel_all_tasks&lt;/code&gt; first, so running tasks stop instead of landing in the dead-letter set.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The API stops freezing on Cancel All
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;POST /queue/cancel-all&lt;/code&gt; ran a synchronous connect → SELECT → commit → disconnect on the event loop, once per task. After a large import that is thousands of cycles inside one request with no awaits, and Cortex served nothing else in the meantime. All four call sites now go through a worker thread. The same treatment went to per-chunk progress writes on the worker. A commit that lost the SQLite writer lock could park the loop for up to 60 seconds and starve the heartbeat refresher, which is how live tasks started to look abandoned.&lt;/p&gt;

&lt;p&gt;The vision-page fan-out went from up to 4000 sequential round trips (bounded by &lt;code&gt;vision_max_pages&lt;/code&gt;, default 2000) to 2. The changelog has the rest of the round: projections and unprojected reads fixed across the stuck-source sweep, step progress, queue stats and &lt;code&gt;chaoscypher source search&lt;/code&gt;. These describe what the code was doing, not a benchmark.&lt;/p&gt;

&lt;h3&gt;
  
  
  Upgrading
&lt;/h3&gt;

&lt;p&gt;No migrations apply. Drain the queue before you swap the image: stop new submissions and wait for &lt;code&gt;/api/v1/queue/stats&lt;/code&gt; to report 0 pending on all queues. Don't run mixed versions against the same queue.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull ghcr.io/chaoscypherinc/chaoscypher:0.4.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, if you run the Python packages directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-U&lt;/span&gt; chaoscypher-core chaoscypher-cortex chaoscypher-neuron chaoscypher-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Starting fresh:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; chaoscypher &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 80:80 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 443:443 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; chaoscypher-data:/data &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--add-host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;host.docker.internal:host-gateway &lt;span class="se"&gt;\&lt;/span&gt;
  ghcr.io/chaoscypherinc/chaoscypher:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Next steps
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;If you use MCP with a &lt;code&gt;read&lt;/code&gt;-configured install, upgrade first. That is the fix with the widest reach.&lt;/li&gt;
&lt;li&gt;If you set trigger filters, check your automations after upgrading. Triggers you scoped to one source now fire only on that source.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Full details in the &lt;a href="https://chaoscypher.com/docs/about/changelog" rel="noopener noreferrer"&gt;changelog&lt;/a&gt;. ChaosCypher is AGPL-3.0 and local-first: the graph, the chat, and the import and export paths all run on your own machine. Repo: &lt;a href="https://github.com/chaoscypherinc/chaoscypher" rel="noopener noreferrer"&gt;https://github.com/chaoscypherinc/chaoscypher&lt;/a&gt;&lt;/p&gt;

</description>
      <category>selfhosted</category>
      <category>opensource</category>
      <category>python</category>
      <category>rag</category>
    </item>
    <item>
      <title>Automate Your Knowledge Pipeline: Triggers, Workflows, and AI Tools</title>
      <dc:creator>Denis MacPherson</dc:creator>
      <pubDate>Fri, 18 Sep 2026 14:46:49 +0000</pubDate>
      <link>https://dev.to/chaoscypherinc/automate-your-knowledge-pipeline-triggers-workflows-and-ai-tools-424g</link>
      <guid>https://dev.to/chaoscypherinc/automate-your-knowledge-pipeline-triggers-workflows-and-ai-tools-424g</guid>
      <description>&lt;p&gt;Most knowledge management tools treat you like a filing clerk. Upload a document, wait for extraction, manually review entities, fix errors, tag things, connect things. Then do it all again for the next document. And the next. And the next fifty.&lt;/p&gt;

&lt;p&gt;This is fine when you have ten documents. It falls apart at a hundred. It becomes genuinely painful at a thousand. The bottleneck is never the AI -- it's the human loop. Every document requires your attention, your judgment calls, your clicks. The extraction might take thirty seconds. Your review and cleanup take ten minutes.&lt;/p&gt;

&lt;p&gt;Chaos Cypher's workflow engine exists to close that gap. You define a processing pipeline once -- what to extract, how to validate it, where to send notifications -- and every new document flows through it automatically. No babysitting. No repetitive clicking. You set the rules, the system follows them.&lt;/p&gt;

&lt;p&gt;This isn't a cron job bolted onto the side. It's a proper workflow engine with event-driven triggers, conditional branching, step-to-step data passing, and composable AI tools. Let me walk you through how it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Concrete Workflow: Auto-Processing Research Papers
&lt;/h2&gt;

&lt;p&gt;Abstractions are boring. Let's look at a real workflow you might build: automatically processing research papers as they're uploaded.&lt;/p&gt;

&lt;p&gt;Here's the pipeline:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trigger:&lt;/strong&gt; Extraction commits new entities to the graph (&lt;code&gt;node.create&lt;/code&gt; event fires) -- the signal that a freshly uploaded paper has finished processing. (You can also invoke the workflow manually or through the API.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1 -- AI Prompt:&lt;/strong&gt; Summarize the document in three sentences. The &lt;code&gt;ai.prompt&lt;/code&gt; tool sends the document text to your configured LLM with instructions to produce a concise summary. If the document is long, it automatically chunks the text and processes sections in parallel, then merges the results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 -- AI Extract JSON:&lt;/strong&gt; Pull out structured metadata. Authors, publication date, journal name, key findings, methodology type. The &lt;code&gt;ai.extract_json&lt;/code&gt; tool takes the document text and a JSON schema defining exactly what you want, then returns validated structured data. It retries if the extraction doesn't match the schema.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 -- Conditional:&lt;/strong&gt; Check if this is a clinical study. The &lt;code&gt;logic.conditional&lt;/code&gt; tool evaluates whether &lt;code&gt;{{steps.extract_metadata.extracted_data.methodology_type}}&lt;/code&gt; equals &lt;code&gt;"clinical_trial"&lt;/code&gt;. If true, the workflow branches to run additional &lt;a href="https://chaoscypher.com/blog/domain-extraction-guide" rel="noopener noreferrer"&gt;medical-domain extraction&lt;/a&gt;. If false, it skips ahead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4 -- HTTP Request:&lt;/strong&gt; Post a notification to a Slack webhook with the summary from Step 1 and the metadata from Step 2. The &lt;code&gt;http.request&lt;/code&gt; tool sends a POST to your webhook URL with a JSON body containing &lt;code&gt;{{steps.summarize.result}}&lt;/code&gt; and &lt;code&gt;{{steps.extract_metadata.extracted_data}}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Notice the &lt;code&gt;{{steps.summarize.result}}&lt;/code&gt; syntax. That's the interpolation engine at work. Every step's output is keyed by the step's ID -- reference it as &lt;code&gt;{{steps.&amp;lt;step_id&amp;gt;.&amp;lt;field&amp;gt;}}&lt;/code&gt; from any later step. The IDs here (&lt;code&gt;summarize&lt;/code&gt;, &lt;code&gt;extract_metadata&lt;/code&gt;) are placeholders: IDs are instance-specific, so check a step's actual ID in the builder (or via &lt;code&gt;GET /api/v1/workflows/{id}&lt;/code&gt;) before wiring references -- and re-check after importing a workflow, because the export format does not carry step IDs, so import assigns fresh ones and cross-step references in configurations must be updated. You can reference &lt;code&gt;{{inputs.&amp;lt;field&amp;gt;}}&lt;/code&gt; for the data the workflow was invoked with, &lt;code&gt;{{steps.extract_metadata.extracted_data.authors}}&lt;/code&gt; for a nested field from a previous step, or even &lt;code&gt;{{steps.check_clinical.branch_taken}}&lt;/code&gt; to see which conditional path was followed. The interpolation preserves types too -- if a previous step returned a number, you get a number, not the string &lt;code&gt;"42"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This entire pipeline runs without human intervention. Upload a PDF, walk away, come back to a summarized, metadata-tagged, conditionally-processed document with a Slack notification waiting for you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F280awt2hmpkswiersmbh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F280awt2hmpkswiersmbh.png" alt="Queue monitor showing task status and history" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Under the Hood: 10 Built-In Tools
&lt;/h2&gt;

&lt;p&gt;The workflow engine ships with ten built-in tools organized into five categories. Each tool has a defined input schema and output schema, so the system validates your configuration before anything runs.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Tools&lt;/th&gt;
&lt;th&gt;What They Do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ai.prompt&lt;/code&gt;, &lt;code&gt;ai.extract_json&lt;/code&gt;, &lt;code&gt;ai.vector_search&lt;/code&gt;, &lt;code&gt;ai.generate_embedding&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;LLM interactions with chunking support, structured JSON extraction with schema validation and retries, semantic search across your knowledge graph, vector embedding generation for entities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;data.extract&lt;/code&gt;, &lt;code&gt;data.merge&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Pull values from nested objects using dot-notation paths (&lt;code&gt;user.addresses.0.city&lt;/code&gt;), merge multiple dictionaries with shallow or deep strategies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Logic&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;logic.conditional&lt;/code&gt;, &lt;code&gt;logic.loop&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;If/then branching with safe expression evaluation, iterate over collections with configurable limits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;HTTP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;http.request&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;External API calls with all HTTP methods, bearer/basic auth, configurable timeouts, and SSRF protection that blocks localhost access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Templates&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;templates.list&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Query your knowledge graph schema to discover available node templates&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three details that make these reliable enough to run unattended: &lt;code&gt;ai.prompt&lt;/code&gt; automatically chunks documents that exceed the model's context window, processes the chunks in parallel, and merges the results; &lt;code&gt;ai.extract_json&lt;/code&gt; validates the LLM's output against your JSON schema and retries on mismatch; and &lt;code&gt;http.request&lt;/code&gt; validates URLs before sending (https/http only, localhost blocked against SSRF) while supporting all standard methods and auth schemes. &lt;code&gt;ai.vector_search&lt;/code&gt; is the interesting one for graph-aware pipelines -- workflows can semantically query existing knowledge, enabling steps like "find entities similar to what we just extracted and check for duplicates."&lt;/p&gt;

&lt;h3&gt;
  
  
  How Triggers Work
&lt;/h3&gt;

&lt;p&gt;Triggers are the entry point for automated workflows. They listen for events in the system and fire workflows when conditions are met.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Event sources&lt;/strong&gt; define what happened. Two are live today: &lt;code&gt;node.create&lt;/code&gt; (a new node was committed to the graph) and &lt;code&gt;edge.create&lt;/code&gt; (a new relationship was committed) -- both fire on the extraction-commit path. Other event sources, like file-upload and import-complete, are selectable in the builder but not yet emitted by the engine. The system ships with a built-in auto-embedding trigger -- nodes created when an extraction commits to the graph automatically get vector embeddings generated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Filters&lt;/strong&gt; let you narrow the scope, using exact key-value matching against the event data. A trigger on &lt;code&gt;node.create&lt;/code&gt; with a filter &lt;code&gt;{"entity_type": "node"}&lt;/code&gt; fires only for node events. The &lt;code&gt;node.create&lt;/code&gt; payload currently carries &lt;code&gt;entity_type&lt;/code&gt; and &lt;code&gt;entity_id&lt;/code&gt;, so filters on other keys (like a template id) can never match -- the engine logs a warning when a trigger is wired to a structurally impossible filter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Statistics tracking&lt;/strong&gt; gives you visibility. Every trigger execution records success/failure status, execution time, and error messages. You can see your success rate, average execution time, and recent execution history -- useful for debugging workflows that occasionally fail.&lt;/p&gt;

&lt;h3&gt;
  
  
  The "Expose as AI Tool" Feature
&lt;/h3&gt;

&lt;p&gt;Here's where things get composable. Any workflow can be exposed as a callable AI tool by setting &lt;code&gt;expose_as_ai_tool: true&lt;/code&gt; and defining input/output schemas. Once exposed, that workflow appears alongside the built-in tools and can be used as a step in other workflows.&lt;/p&gt;

&lt;p&gt;Think about what this enables. You build a workflow that extracts and validates medical terminology. You expose it as a tool. Now your "process research papers" workflow can call it as Step 3 instead of hardcoding medical-domain logic. You have a workflow that enriches person entities by cross-referencing external APIs? Expose it, and any other workflow can use it.&lt;/p&gt;

&lt;p&gt;Workflows calling workflows. Each one focused on a single job, composed together into pipelines of arbitrary complexity. The step type &lt;code&gt;workflow&lt;/code&gt; (alongside &lt;code&gt;system_tool&lt;/code&gt; and &lt;code&gt;user_tool&lt;/code&gt;) tells the engine to execute another workflow as a step, passing inputs and receiving outputs just like any other tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  Workflow Portability
&lt;/h3&gt;

&lt;p&gt;Workflows export to a version-stamped, self-describing JSON file -- the definition, all steps, all configurations, no hidden state, no implicit database IDs. Import validates the version and resolves every referenced tool against the target instance's registry before creating anything, so a missing tool produces a clear error instead of a broken workflow. Export from your laptop, import on a server, share with a colleague.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;The fastest way to see the workflow engine in action is to look at the export format. Here's a minimal workflow that summarizes documents on upload:&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;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"workflow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Summarize on Upload"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Auto-summarize new documents when uploaded"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"input_schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"document_text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The document content to summarize"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"document_text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"output_schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Three-point summary of the document"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"steps"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"step_number"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Summarize Document"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"tool_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"system_tool"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"tool_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;"ai.prompt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"configuration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"prompt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Summarize this document in 3 key points:&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;{{inputs.document_text}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"output_format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="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;This is everything the system needs. The &lt;code&gt;version&lt;/code&gt; field ensures forward compatibility. The &lt;code&gt;input_schema&lt;/code&gt; and &lt;code&gt;output_schema&lt;/code&gt; define the contract. The &lt;code&gt;steps&lt;/code&gt; array contains the pipeline.&lt;/p&gt;

&lt;p&gt;Each step specifies its &lt;code&gt;tool_type&lt;/code&gt; (&lt;code&gt;system_tool&lt;/code&gt;, &lt;code&gt;user_tool&lt;/code&gt;, or &lt;code&gt;workflow&lt;/code&gt;), a &lt;code&gt;tool_id&lt;/code&gt; that references a registered tool, and a &lt;code&gt;configuration&lt;/code&gt; object whose shape matches the tool's input schema. The &lt;code&gt;{{inputs.document_text}}&lt;/code&gt; template variable gets resolved at execution time with the inputs the workflow was invoked with.&lt;/p&gt;

&lt;p&gt;To wire it up, create a trigger with the event source (like &lt;code&gt;node.create&lt;/code&gt;), link it to your workflow, and optionally add filters. From there the engine scales with you: steps can declare &lt;code&gt;depends_on&lt;/code&gt;, &lt;code&gt;continue_on_error&lt;/code&gt;, &lt;code&gt;max_retries&lt;/code&gt; (per-step, falling back to the workflow-level default), and &lt;code&gt;timeout_seconds&lt;/code&gt;; imports handle name conflicts (&lt;code&gt;fail&lt;/code&gt;/&lt;code&gt;skip&lt;/code&gt;/&lt;code&gt;rename&lt;/code&gt;) and can land inactive for a test run first. The &lt;a href="https://chaoscypher.com/docs/reference/api/workflows" rel="noopener noreferrer"&gt;workflows API reference&lt;/a&gt; documents the step fields and import options.&lt;/p&gt;

&lt;p&gt;One more thing worth knowing before you trust a pipeline to run unattended: every run produces an execution record -- status, inputs, outputs, per-step timing, and which step failed and why. When a workflow fails at 3am, you don't guess. You look at the execution detail, see that Step 3 timed out after 120 seconds waiting for the LLM, and adjust accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;The workflow engine is designed to grow. The tool system uses a plugin architecture -- the same pattern that powers Chaos Cypher's loader plugins, domain plugins, and LLM providers. Custom tool plugins in Python shipped: drop a &lt;code&gt;*_plugin.py&lt;/code&gt; file in &lt;code&gt;data/plugins/tools/&lt;/code&gt; implementing &lt;code&gt;tool_id&lt;/code&gt;, &lt;code&gt;category&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;input_schema&lt;/code&gt;, and an &lt;code&gt;execute&lt;/code&gt; method, and it auto-registers on startup (user plugins even override built-ins with the same &lt;code&gt;tool_id&lt;/code&gt;). See the &lt;a href="https://chaoscypher.com/docs/user-guide/tool-plugins" rel="noopener noreferrer"&gt;tool plugins guide&lt;/a&gt; for the full interface.&lt;/p&gt;

&lt;p&gt;More trigger event sources are coming as the platform grows. Scheduling (run a workflow every Tuesday at 9am) and webhook triggers (fire a workflow from an external system) are natural extensions of the existing event-driven architecture.&lt;/p&gt;

&lt;p&gt;If you've built an interesting automation workflow -- whether it's a multi-step research pipeline, a quality assurance checker, or an integration with external tools -- I'd genuinely like to hear about it. The export format makes sharing straightforward: export your workflow, share the JSON, and someone else can import it and adapt it to their use case. That's the whole point of portability.&lt;/p&gt;

&lt;p&gt;For the full API reference and detailed configuration options, check out the &lt;a href="https://chaoscypher.com/docs/reference/api/workflows" rel="noopener noreferrer"&gt;workflow documentation&lt;/a&gt;. The built-in system workflows (like auto-embedding on node create) are also good starting points -- export them and study the step configurations to see how the engine's own automation is wired together.&lt;/p&gt;

</description>
      <category>workflows</category>
      <category>automation</category>
      <category>ai</category>
      <category>tutorials</category>
    </item>
    <item>
      <title>From Obsidian Vault to AI-Powered Knowledge Graph in Minutes</title>
      <dc:creator>Denis MacPherson</dc:creator>
      <pubDate>Fri, 18 Sep 2026 14:46:28 +0000</pubDate>
      <link>https://dev.to/chaoscypherinc/from-obsidian-vault-to-ai-powered-knowledge-graph-in-minutes-2dm1</link>
      <guid>https://dev.to/chaoscypherinc/from-obsidian-vault-to-ai-powered-knowledge-graph-in-minutes-2dm1</guid>
      <description>&lt;p&gt;Open your Obsidian graph view and look at it honestly: every line on that screen is a link you typed by hand. The person mentioned in forty notes but never &lt;code&gt;[[linked]]&lt;/code&gt;, the concept that appears under three different names, the two projects connected by a decision you wrote down once and forgot -- none of it shows up. Your vault knows more than its graph does.&lt;/p&gt;

&lt;p&gt;Chaos Cypher adds the missing layer. It reads the Markdown itself, extracts entities and relationships with an LLM, and makes the whole vault searchable and chat-ready -- without requiring a single connection to be pre-linked. Your manual wiki-links stay exactly what they are: deliberate structure. The AI-discovered layer sits alongside them, surfacing the connections you never typed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Workflow
&lt;/h2&gt;

&lt;p&gt;No plugin required -- the archive loader does the work:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Zip the notes you want to analyze.&lt;/strong&gt; Start with a focused folder of at least ~10 notes rather than the entire vault, and leave out private notes you don't want processed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upload the ZIP (or TAR.GZ) as a source.&lt;/strong&gt; The archive loader recognizes Markdown-heavy projects, strips frontmatter, preserves heading structure, and sends each note through the same pipeline used for PDFs and web pages. Smaller archives are processed by the generic loader, which keeps frontmatter intact -- bundle at least ten notes to engage the Markdown handler's frontmatter stripping.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff8zz473pp5qlkhncvd8m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff8zz473pp5qlkhncvd8m.png" alt="Add Source dialog with URL input and file drag-and-drop" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Confirm the detected domain.&lt;/strong&gt; Indexing (chunking + local embeddings) takes seconds and immediately enables semantic search and RAG chat. After indexing, Chaos Cypher analyzes your notes and proposes an &lt;a href="https://chaoscypher.com/docs/user-guide/domains" rel="noopener noreferrer"&gt;extraction domain&lt;/a&gt; -- review and confirm (or override) it in the dialog, and extraction starts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Wait for extraction.&lt;/strong&gt; Extraction builds the graph: typed entities and relationships pulled from the prose itself.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Explore what the AI found.&lt;/strong&gt; People mentioned across multiple notes, concepts that appear under different headings, projects connected by shared decisions, recurring themes that were never explicitly linked -- now visible, navigable, and traceable back to the exact notes they came from.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F02uolct3m935zqakcdhn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F02uolct3m935zqakcdhn.png" alt="Knowledge graph visualization showing extracted entities and relationships" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ask questions.&lt;/strong&gt; "What decisions led to the current architecture?" or "Where do my notes on attention mechanisms and retrieval overlap?" -- answered with citations back to your own notes, not the internet.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Scripting the Import
&lt;/h2&gt;

&lt;p&gt;The steps above walk through the UI, but the same pipeline is reachable without opening a browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For developers:&lt;/strong&gt; &lt;code&gt;POST /api/v1/sources&lt;/code&gt; takes a multipart file upload and runs it through the identical loader dispatch the UI uses -- point it at your vault archive and it works the same way. &lt;code&gt;POST /api/v1/sources/batch&lt;/code&gt; accepts a list of files if you're importing several exports at once, which is handy wired into a nightly vault-backup job: export, POST the archive, let indexing and extraction pick it up. In plain English: anything the "Upload" button does, a script can do too, over HTTP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For CLI users:&lt;/strong&gt; the same import is one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;chaoscypher &lt;span class="nb"&gt;source &lt;/span&gt;add my-vault.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;source add&lt;/code&gt; isn't Obsidian-specific -- it's the general-purpose source-loading command, and it routes to the archive loader the same way the web UI does whenever it sees a &lt;code&gt;.zip&lt;/code&gt; or &lt;code&gt;.tar.gz&lt;/code&gt;. There's no separate "import vault" subcommand, because none is needed. In plain English: if you'd rather stay in the terminal, one command gets your vault into the graph.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Few Tips for a Good First Pass
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Keep filenames and headings descriptive -- they become context the extractor uses.&lt;/li&gt;
&lt;li&gt;If your vault has a specialized vocabulary, review the extracted graph and then tune &lt;a href="https://chaoscypher.com/docs/user-guide/domains" rel="noopener noreferrer"&gt;domains or templates&lt;/a&gt; to match it.&lt;/li&gt;
&lt;li&gt;Everything runs locally if you pair it with &lt;a href="https://chaoscypher.com/blog/local-ai-knowledge-graph" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; -- your journal never leaves your machine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This workflow shines wherever the value isn't only in the notes but in the relationships between them: research notes, project journals, meeting archives, long-running personal knowledge bases.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;A first-class Obsidian importer -- reading vaults directly, preserving wiki-links as graph edges alongside the AI-discovered ones -- is on the roadmap. If that's your use case, &lt;a href="https://github.com/chaoscypherinc/chaoscypher/discussions" rel="noopener noreferrer"&gt;tell us how you'd want it to work&lt;/a&gt;; vault structures vary wildly and real examples shape the design.&lt;/p&gt;

</description>
      <category>obsidian</category>
      <category>graphrag</category>
      <category>selfhosted</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Extract Smarter: How Domain-Aware AI Builds Better Knowledge Graphs</title>
      <dc:creator>Denis MacPherson</dc:creator>
      <pubDate>Fri, 11 Sep 2026 14:47:08 +0000</pubDate>
      <link>https://dev.to/chaoscypherinc/extract-smarter-how-domain-aware-ai-builds-better-knowledge-graphs-252d</link>
      <guid>https://dev.to/chaoscypherinc/extract-smarter-how-domain-aware-ai-builds-better-knowledge-graphs-252d</guid>
      <description>&lt;p&gt;Most AI extraction tools treat every document the same way. Upload a medical paper or a legal contract and you get the same generic entity types, the same vague relationships, the same disappointing graph. Chaos Cypher takes a different approach: it detects what kind of document you uploaded and adapts its entire extraction pipeline to match.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Generic Extraction
&lt;/h2&gt;

&lt;p&gt;Here's a sentence you might find in a clinical document:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Patient with hypertension started on lisinopril 10mg daily. The ACE inhibitor is contraindicated with potassium supplements. Side effects include dry cough and dizziness.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A generic extraction pipeline -- the kind most tools use -- will pull out a handful of entities and connect them with whatever relationship labels the LLM feels like inventing. You might get "Lisinopril" typed as an &lt;strong&gt;Item&lt;/strong&gt;, "Hypertension" as a &lt;strong&gt;Concept&lt;/strong&gt;, and "Dry Cough" as another &lt;strong&gt;Concept&lt;/strong&gt;. The relationships between them? Probably &lt;code&gt;related_to&lt;/code&gt; and &lt;code&gt;influences&lt;/code&gt;. Maybe &lt;code&gt;associated_with&lt;/code&gt; if you are lucky.&lt;/p&gt;

&lt;p&gt;This is the "garbage in, garbage out" of knowledge graphs. It's not that the AI failed to read the text. It read it fine. The problem is that nobody told it what to look for, what types are valid, or what the relationships between those types should mean.&lt;/p&gt;

&lt;p&gt;The graph you get is technically correct and practically useless. You cannot query "which drugs treat hypertension" because the system does not know what a Drug is. You cannot find contraindications because &lt;code&gt;related_to&lt;/code&gt; could mean anything. Every edge in the graph carries the same semantic weight as a shrug.&lt;/p&gt;

&lt;p&gt;Now run the same sentence through Chaos Cypher with the &lt;strong&gt;medical&lt;/strong&gt; domain active:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lisinopril&lt;/strong&gt; becomes a &lt;strong&gt;Drug&lt;/strong&gt; with dosage form and mechanism of action as properties&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hypertension&lt;/strong&gt; becomes a &lt;strong&gt;Condition&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dry Cough&lt;/strong&gt; and &lt;strong&gt;Dizziness&lt;/strong&gt; become &lt;strong&gt;Side Effects&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Potassium Supplements&lt;/strong&gt; gets recognized as a &lt;strong&gt;Drug&lt;/strong&gt; (because supplements have drug interactions too)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The relationships are just as precise: &lt;code&gt;treats&lt;/code&gt;, &lt;code&gt;contraindicated_with&lt;/code&gt;, &lt;code&gt;produces_side_effect&lt;/code&gt;. Each one is typed, directional, and constrained. A &lt;code&gt;treats&lt;/code&gt; edge must start from a therapeutic agent (Drug, Treatment, Procedure, Protocol, or Guideline) and land on a clinical target (Condition, Symptom, Side Effect, Pathogen, or Patient Population). A Side Effect can never treat a Drug. The LLM isn't guessing -- it's following a schema.&lt;/p&gt;

&lt;p&gt;That's what domain-aware extraction does. It turns a language model from a general-purpose pattern matcher into a domain specialist.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fax6my2fv0kg6bdttql56.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fax6my2fv0kg6bdttql56.png" alt="Source detail showing entity and relationship distribution charts" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works: Upload to Knowledge Graph
&lt;/h2&gt;

&lt;p&gt;The workflow is straightforward. You upload a document. Chaos Cypher figures out what domain it belongs to, loads the right extraction rules, proposes the domain for your confirmation, then runs the pipeline. You don't need to configure anything upfront -- though you can override the detected domain if you want.&lt;/p&gt;

&lt;p&gt;:::info Update (June 2026)&lt;br&gt;
Since the domain-confirmation gate shipped, auto-detection proposes a domain and parks the source awaiting your one-click confirmation before the (potentially hour-long) extraction runs. You can accept, override the proposed domain, or pass &lt;code&gt;auto_confirm&lt;/code&gt; at upload to restore the fully hands-off behavior described below.&lt;br&gt;
:::&lt;/p&gt;

&lt;p&gt;Here's what happens behind the scenes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Detection&lt;/strong&gt; -- Chaos Cypher samples up to ~12,000 characters drawn from the beginning and the middle of your document and scores it against all registered domains simultaneously. Each domain has weighted keyword groups, regex patterns, and file type signals. The highest-scoring domain wins.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Guidance injection&lt;/strong&gt; -- The winning domain's extraction rules get injected into the LLM prompt. This includes entity type definitions, relationship constraints, exclusion rules (what &lt;em&gt;not&lt;/em&gt; to extract), and worked examples of correct extractions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Strict type enforcement&lt;/strong&gt; -- The LLM is instructed to only use entity types from the domain's template list. After extraction, a code-level filter drops any entity whose type does not match a known template. No hallucinated types survive.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Relationship validation&lt;/strong&gt; -- Each relationship is checked against source/target type constraints. A &lt;code&gt;treats&lt;/code&gt; relationship must flow from a therapeutic agent like a Drug, Treatment, or Procedure to a clinical target like a Condition or Symptom. Anything else gets rejected.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Quality scoring&lt;/strong&gt; -- Extracted entities and relationships are scored by domain relevance. Domain-specific types like Drug and Condition score higher than generic fallbacks. This surfaces the most valuable parts of your graph.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff8zz473pp5qlkhncvd8m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff8zz473pp5qlkhncvd8m.png" alt="Add Source dialog with URL input and file drag-and-drop" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Chaos Cypher ships with &lt;strong&gt;19 built-in domains&lt;/strong&gt;, each tuned for a different category of document. A sample:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Domain&lt;/th&gt;
&lt;th&gt;Typical Entity Types&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;strong&gt;Medical&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Drug, Condition, Symptom, Procedure, Side Effect&lt;/td&gt;
&lt;td&gt;Clinical documents, pharmaceutical literature&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Technical&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Module, Class, Function, Endpoint, Design Pattern&lt;/td&gt;
&lt;td&gt;API docs, codebases, technical specifications&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Legal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Statute, Case, Party, Obligation, Legal Principle&lt;/td&gt;
&lt;td&gt;Contracts, court opinions, regulatory filings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Financial&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Company, Financial Instrument, Market Event, Regulation&lt;/td&gt;
&lt;td&gt;Earnings reports, market analysis, SEC filings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scientific&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hypothesis, Method, Finding, Dataset, Organism&lt;/td&gt;
&lt;td&gt;Research papers, experiments, academic publications&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cybersecurity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Threat Actor, Vulnerability, Malware, Attack Technique&lt;/td&gt;
&lt;td&gt;Threat intel, incident reports, CVE research&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Generic&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Person, Organization, Event, Concept, Location&lt;/td&gt;
&lt;td&gt;General-purpose fallback for any content&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The rest cover historical, literary, theological, investigative, political, design, intelligence, news, educational, philosophical, biographical, and reference material — the &lt;a href="https://chaoscypher.com/docs/user-guide/domains#built-in-domains" rel="noopener noreferrer"&gt;full list with every entity type is in the docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Every domain uses strict entity type enforcement by default. The medical domain defines 20 entity types. The technical domain has 18. These aren't suggestions -- they're the only types the LLM is allowed to produce. That constraint is what separates a clean, queryable graph from a noisy soup of ad-hoc labels.&lt;/p&gt;
&lt;h2&gt;
  
  
  Under the Hood: Domain Detection and Extraction Quality
&lt;/h2&gt;
&lt;h3&gt;
  
  
  How Detection Works
&lt;/h3&gt;

&lt;p&gt;Domain detection runs a scoring algorithm across all registered domains simultaneously. Each domain defines its detection rules in a JSON-LD config file with three signal types:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Weighted keyword groups.&lt;/strong&gt; The medical domain has six keyword groups: &lt;code&gt;clinical_core&lt;/code&gt; (weight 1.2), &lt;code&gt;pharmaceutical&lt;/code&gt; (weight 1.0), &lt;code&gt;diagnostic&lt;/code&gt; (weight 0.9), &lt;code&gt;anatomy&lt;/code&gt; (weight 0.8), &lt;code&gt;procedures&lt;/code&gt; (weight 0.9), and &lt;code&gt;clinical_terms&lt;/code&gt; (weight 0.8). Each keyword match boosts the confidence score by &lt;code&gt;per_keyword_boost * weight&lt;/code&gt;. A document full of "diagnosis", "treatment", and "symptoms" racks up points fast in the clinical_core group, while scattered mentions of "cardiac" and "pulmonary" add smaller anatomy-weighted boosts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Regex patterns.&lt;/strong&gt; Keywords catch common terms, but patterns catch domain-specific notation. The medical domain matches dosage expressions like &lt;code&gt;\d+\s*(mg|mcg|ml)&lt;/code&gt;, ICD codes like &lt;code&gt;ICD-10:J45&lt;/code&gt;, and prescription abbreviations like &lt;code&gt;b.i.d.&lt;/code&gt; and &lt;code&gt;p.r.n.&lt;/code&gt;. Each pattern match carries its own weight -- dosage notation at 1.4x, ICD codes at 1.5x. A single ICD code in a document is a strong medical signal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File and document type signals.&lt;/strong&gt; File extensions (&lt;code&gt;.py&lt;/code&gt; for technical) and document type metadata (&lt;code&gt;medical_document&lt;/code&gt;, &lt;code&gt;openapi&lt;/code&gt;) provide additional boosts.&lt;/p&gt;

&lt;p&gt;The final confidence score is compared against a per-domain minimum threshold. Medical requires 0.4 minimum confidence. The generic domain has a threshold of 0.0 -- it always matches as a fallback, but with the lowest possible score (0.1), so any specialized domain that passes both its own minimum threshold and the registry's absolute confidence floor of 1.0 will win. The floor exists so weak, ambiguous matches fall back to the broad generic schema instead of locking in a wrong domain.&lt;/p&gt;
&lt;h3&gt;
  
  
  How Domains Shape Extraction Quality
&lt;/h3&gt;

&lt;p&gt;Detection picks the right domain. But the real value is in what happens next -- the selected domain controls the extraction pipeline at five points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Entity guidance&lt;/strong&gt; tells the LLM what to extract &lt;em&gt;and what to skip&lt;/em&gt;. The medical domain says "include dosage information as properties on drug entities" and explicitly excludes standalone dosage numbers, study references ("Figure 1"), and administrative codes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strict type enforcement&lt;/strong&gt; gives the LLM a closed list of valid types -- the medical domain allows exactly 20. Anything outside that list gets dropped in post-processing. No more "Medical Concept" or "Health Thing" cluttering your graph.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Relationship constraints&lt;/strong&gt; validate source and target: &lt;code&gt;treats&lt;/code&gt; must flow from a therapeutic agent like a Drug, Treatment, or Procedure to a clinical target like a Condition or Symptom. A Symptom that "treats" a Drug fails validation. This catches the most common extraction error -- reversed or nonsensical edges.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compatibility groups&lt;/strong&gt; handle deduplication: "Hypertension" extracted as a Condition in one chunk and a vaguer type in another can be merged because both belong to the &lt;code&gt;clinical&lt;/code&gt; group -- no duplicates, no lost type precision.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Property type mapping&lt;/strong&gt; rescues mistyped entities: "Severity" extracted as a standalone node gets absorbed into the right Condition as a &lt;code&gt;severity&lt;/code&gt; property instead of floating as an orphan.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In plain English: the domain is a contract. The LLM does the reading, but the domain decides what counts as a valid entity, a valid relationship, and a valid graph -- and everything that breaks the contract is filtered out before it reaches you.&lt;/p&gt;
&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;Every built-in domain is just a JSON-LD file. No Python, no compilation, no framework code. If you need a domain for your field that doesn't exist yet, you can create one in about 20 minutes: drop a file in &lt;code&gt;data/plugins/domains/&lt;/code&gt;, define your detection signals and your vocabulary, restart, done.&lt;/p&gt;

&lt;p&gt;Say you want a &lt;strong&gt;startup&lt;/strong&gt; domain for pitch decks and funding announcements. The skeleton is just three parts -- how to recognize the content, what entities exist, and how they're allowed to connect:&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;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ExtractionDomain"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"startup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"strict_entity_types"&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;"detection"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"base_score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"per_keyword_boost"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"keywords"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"funding"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"terms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"series A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"series B"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"seed round"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"venture capital"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="s2"&gt;"valuation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"term sheet"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cap table"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pre-money"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"weight"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.3&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"patterns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"regex"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;$&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;d+[MBK]&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;s+(seed|series|round)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"weight"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"templates"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"node_templates"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"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;"startup_company"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Company"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"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;"startup_round"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Funding Round"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"edge_templates"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"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;"startup_raised"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"raised"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"source_types"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Company"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"target_types"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Funding Round"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="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;Detection has an absolute confidence floor of 1.0 -- a domain that scores below it loses to the generic fallback, so give a new domain enough keyword surface to clear it, or force the domain at upload while you tune detection. (The skeleton above clears it: eight keywords at a 0.12 boost and 1.3 weight put a keyword-rich article at roughly 1.45 before patterns even fire.)&lt;/p&gt;

&lt;p&gt;Upload a TechCrunch article and watch the detection engine propose it at the confirmation step: confirm, and Companies and Funding Rounds appear in your graph, connected only by the relationships you allowed.&lt;/p&gt;

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

&lt;p&gt;The full walkthrough -- weighted keyword groups, named-referent flags, entity properties, inverse relationships, and a complete worked example -- lives in the docs: start with &lt;a href="https://chaoscypher.com/docs/user-guide/domains#custom-domains" rel="noopener noreferrer"&gt;Custom Domains&lt;/a&gt;, then &lt;a href="https://chaoscypher.com/docs/developer-guide/building-domains" rel="noopener noreferrer"&gt;Building Extraction Domains&lt;/a&gt; for the whole schema, including normalization keywords, compatibility groups, property absorption, and extraction-density tuning. The built-in medical domain is the most comprehensive reference -- study it when you want the full picture.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;We're planning more specialized domains -- supply chain, environmental science, and music theory are on the shortlist. But the real potential is in what users build. Every field has its own vocabulary, its own entity types, its own relationship patterns. A materials scientist cares about Crystal Structure, Synthesis Method, and Property. A genealogist needs Person, Family, Vital Record, and Census Entry. A cybersecurity analyst -- who already has a built-in domain -- might want to fork it and add types specific to their organization's threat model.&lt;/p&gt;

&lt;p&gt;If you build a domain for your field, share it. A JSON-LD file is small, portable, and easy to review. Drop it in &lt;code&gt;data/plugins/domains/&lt;/code&gt; and it works. No pull request required to use it, but we would love to include community domains in the built-in set for others to benefit from.&lt;/p&gt;

&lt;p&gt;Domains work identically whether you're running &lt;a href="https://chaoscypher.com/blog/local-ai-knowledge-graph" rel="noopener noreferrer"&gt;locally with Ollama&lt;/a&gt; or with a cloud provider. Start with the skeleton above, test it on your documents, and iterate from there.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>graphrag</category>
      <category>tutorial</category>
      <category>python</category>
    </item>
  </channel>
</rss>
