/context contexts. The knowledge graph ties all of them together the way an Obsidian vault does. Every item is a note, notes point at each other through typed, directed links, every note has backlinks, and [[wikilinks]] work anywhere.
The graph serves three things:
- The model navigates it with
@memory neighbors(and the## Related (graph)section of every@memory recall),@memory linkand@knowledge links. - You open it as an Obsidian vault: ChatCLI keeps one Markdown note per graph note in sync in
~/.chatcli/vault. See The vault. - The per-turn index card (counts by kind plus hubs) tells the model what exists, so it knows what to pull.
~/.chatcli/memory/graph.json; the vault is a projection of the graph. The only graph input that is not derived is links.json, which holds the links you draw on purpose.
Notes
Tags are glue: they link notes that share them, but they are never listed as related notes. In the vault they become front-matter
tags, not notes.
Links
A link has a direction (the note that writes it points at the target), a relation and an origin.
The weights of every relationship the graph had before typed links existed are unchanged, so recall ranks exactly as it did; the typed links add direction, a reason and new connections on top.
Wikilinks and name resolution
[[target]], [[target|label]], [[target#heading]], [[target^block]] and embeds (![[target]]) are links wherever they are written: in a fact you asked ChatCLI to remember, a topic summary, an episode, a daily note, a card, a document. The target resolves to a note by:
- the exact node id (
topic:auth); - a title or alias, compared with accents removed, case folded and spaces,
-and_treated alike ([[Configuração do servidor]]=[[configuracao-do_servidor]]).
Autolinked mentions
Prose links itself. When the title or alias of a project, topic, skill or context appears in the text of a fact, topic, episode, project, card or journal note, that note gets amentions link to it (Obsidian’s “unlinked mentions”, resolved automatically). The rules keep it precise:
- the name must appear as whole words (
Orbitaldoes not matchOrbitals); - names shorter than 3 characters, purely numeric names, and single words shorter than 4 characters or too generic (
user,project,test,code,dados,projeto…) are not autolinked; - a mention never restates a link the two notes already have;
- at most 8 mentions per note; the longest name wins at a position (
Kubernetes operatoroverKubernetes).
Explicit links
Some relationships are only in your head: “this gotcha is about that project”, “this document explains that topic”. Draw them, and they are recalled with either note.- The model (@memory)
- You (/memory)
from/to also accept source/target, rel also relation; relate, connect and disconnect work as subcommand names. link and unlink change memory, so they go through the confirmation policy like remember.@memory list shows it, a title or alias, a [[wikilink]], or free text that names exactly one note. A reference that could mean several notes is refused with the candidates and their ids, so a link is never drawn to the wrong note.
The relation is a free name stored as a slug (Depends On → depends_on, default related); common words map onto the built-in relations (mention → mentions, references → links_to, belongs_to → part_of, project → in_project).
Explicit links live in ~/.chatcli/memory/links.json:
- sealed at rest, written atomically, merged across processes under a file lock, quarantined if corrupt, read-only when sealed with a key this process does not have — like every memory store;
- a removal is a tombstone kept for 30 days, so another process never brings a removed link back;
- a link drawn to a fact that is later superseded follows the newer fact automatically, and memory compaction moves links from the facts it rewrites to the consolidated fact that restates them;
- a link whose note is gone from the stores is kept for 30 days (a context re-created, a fact re-learned) and then dropped by memory compaction. Notes that are only outside the graph’s size caps (older sessions, older episodes, documents past the cap) still exist and never lose their links; tags and unknown ids are never collected;
/memory exportand/memory importcarry links; fact ids are content hashes, so links survive moving memory to another machine;- at most 10,000 links; a note on a link is one line of at most 280 bytes (longer notes are cut with an ellipsis).
unlink removes only explicit links. A link that comes from the data itself (a shared tag, the fact’s project, a wikilink, a mention) is reported with its relation and origin: change the data to change it.
Context documents
A context is not one opaque blob in the graph: every document in it is a note. Chunks of a@docs-flatten corpus fold back into the document they came from.
- Title: the front-matter
title, else the first heading, else the file name. Summary: the first prose line (up to 200 characters). - Markdown links between documents become
links_tolinks: relative paths resolve against the document’s own folder;#anchorsand?queriesare ignored; the extension is optional (.md,.mdx,.markdown,.html); a link to a folder lands on itsREADMEorindex; a site-absolute link (/guide/setup) resolves from the corpus root or by a unique path suffix. Links to URLs,mailto:,data:and in-page anchors are not document links. - Wikilinks in a document resolve against the document titles and file names of the same context first, then against the whole vault: a page can link to a memory topic or project.
- Bounds: up to 2,000 documents per context and 6,000 in the whole graph (the contexts you created first keep theirs), and up to 64 links per document. Every document stays searchable through retrieval either way.
- Windows paths are handled: scanned contexts keep
\in their paths, links inside documents use/.
@knowledge:
docs/gateway.md#0002) works as source too. backlinks, related and see_also are accepted as names of the subcommand. Every @knowledge search also ends with up to 6 linked documents of the top results that are not among them:
@memory neighbors: the local graph
neighbors shows a note and what is around it, two hops deep, with the relation of every direct neighbor: → for links the note writes, ← for backlinks.
[[wikilink]], which name a note exactly; other text falls back to the best keyword match. @memory map gives the overview: node counts by kind and the hub notes. The legacy names links, backlinks and related still mean neighbors.
Recall uses the graph. The top facts of every @memory recall (and of full mode injection) are expanded one hop, and the structurally adjacent notes that ranking missed go into a ## Related (graph) section; the proactive auto-recall adds a one-line Related (graph): pointer. See Bootstrap and Memory.
The index card injected each turn lists node counts by kind and up to 8 hubs. Documents are counted but never listed as hubs, so a densely linked corpus does not crowd your own projects and topics out of the card.
@graphview with source=knowledge draws the whole graph; selecting an edge shows the relations behind it (about · mentions). See Interactive graph.
The Obsidian vault
ChatCLI keeps the graph as an Obsidian-compatible vault: open~/.chatcli/vault as a vault in Obsidian (or read it in any editor or with git).
## Links section:
- Links list the note’s outgoing links grouped by relation, as wikilinks with the exact note path (
[[Folder/Note|Title]]); Obsidian computes the backlinks. A link you drew says· linked by you, with its note. - Wikilinks inside the text (a fact, a document, a daily note) are rewritten to the exact path of the note the graph resolves them to, keeping
#headingand|label, so they work in Obsidian even when the target is an alias, a title that differs from its file name, or a note in another folder. Fenced code blocks are left alone; a target that names nothing stays as written. - Documents carry their full content (up to 1 MiB per note) after a
> Source:line, with secrets masked by the same redactor the model path uses; source code is shown in a fenced block and never rewritten (links and[[…]]are parsed only in Markdown, outside fenced code). A document’s path is kept relative to the folder its context’s documents share. Journal notes carry their full text; the profile lists its fields.
Sync
The vault syncs in the background: a few seconds (5 s of quiet, at most 60 s) after memory changes, and 10 s after start. It is incremental. A note is rewritten only when its content changed, and a document is not even re-read unless its context or its links changed; on a synthetic store of about 2,800 notes the first sync took about 2.1 s and a sync with nothing to write about 0.4 s. It is safe to work in the vault:- Your edits are kept. A note you changed is not overwritten and not removed;
/memory vaultlists it as kept./memory vault sync --forceregenerates ChatCLI’s own notes. - Files ChatCLI did not write are never touched, even with
--force: add your own notes, canvases and attachments freely. - A note ChatCLI no longer produces is removed only if you did not edit it, and empty folders go with it.
- Writes are atomic; files are
0600and folders0700. A sync stopped by shutdown removes nothing, and a corrupt manifest makes the next sync keep every file. - File names are portable: characters that Windows, Obsidian or link syntax reject are replaced, names are bounded, and two notes whose names differ only by case get distinct files.
Commands and settings
/config memory shows the explicit link count, both vault variables and the last sync. The vault is generated in every mode, the server included. In Kubernetes, the Helm chart has memory.vault.enabled and memory.vault.dir, and the operator spec.features.memory.vault; at the default path the vault is not on a persistent volume, so it is rebuilt on every start.
Upgrading
Nothing to do. The stores keep their format and nothing is converted. Agraph.json written by an older version (schema 1) is discarded on the first start and rebuilt from the stores, now with typed links; links.json and the vault start empty and fill on their own. A test that builds the same memory with the previous graph builder and the new one checks that every note and every link users had before is still there, with at least the same weight.
On the same synthetic store (about 2,800 notes and 7,900 links, 2,000 of them documents), building the graph took about 28 ms the first time and 8 ms on later rebuilds, and graph.json was about 3.2 MB; the file holds the typed links and only the adjacency they do not explain.