Repository navigation
Data and Backup
All memory lives in one directory, ~/.knowledge-graph/ by default (set
KG_STORAGE_ROOT to move it). It is plain JSON plus a few append-only JSONL
logs, so any file tool can read, copy or back it up. Uninstalling kg-memory
leaves it in place.
| Data | Path (under ~/.knowledge-graph/) |
What it holds |
|---|---|---|
| User graph |
user.json (+ user.prev) |
Cross-project knowledge |
| Project graphs |
projects/<slug>/graph.json (+ graph.prev) |
One graph per project |
| Tool-event counters | projects/<slug>/tool_events.json |
Per-file read and fetch counts that feed the capture nudges and the activity part of the DEBT score. Rebuilds from zero if lost |
| Maintenance memory |
maintain.json (+ maintain.prev) |
The maintenance agent's own lessons. Never preloaded, rendered by a full read or searched |
| Sessions | sessions.json |
Session registry: what each session has seen, view times, read parts not yet delivered, Antigravity delivery queues |
| Project renames | aliases.json |
Old project slugs mapped to new ones after a project folder was renamed |
| Upkeep settings and state |
chores.json, chore_state.json
|
Your background-upkeep switch and settings; dispatch spacing and daily counts |
| Decision logs |
recall.jsonl, useful.jsonl, chores.jsonl (+ .prev) |
What recall decided, endorsements and credits, every upkeep decision. Each is size-capped (8, 2 and 4 MB) and rolls over into one .prev file |
| Antigravity runner | agy-runner/ |
Workspace of the Antigravity maintenance runner, if you use it |
| Version history | .git/ |
Optional; see Versioned History |
The kg command keeps its runtime state separately, in
~/.local/state/knowledge-graph/ (or $XDG_STATE_HOME/knowledge-graph/):
server.pid, start.lock, last_start_error (why the last start failed),
mcp_server.log (when kg started the server; under the systemd service the
log is in the journal, which kg logs reads), visual_editor.log and
editor.pid from kg editor, and backups/ with every file kg setup or
kg uninstall changed. A server on a port other than 8765 keeps these files in
port-<N>/ inside that directory. Nothing in the state directory is memory;
it is safe to lose.
A project graph is keyed by the final component of the project's directory
path. For example, /home/user/DevProj/my-app maps to projects/my-app/.
The slug carries no path hash, so two project roots with the same final
directory name share one graph. Use distinct directory names within one
storage root.
Project roots must be inside your home directory. A session started in the home directory itself (or in a folderless Codex app chat) works with user memory only, unless a graph already exists for that directory.
If you rename a project folder, the server finds the old graph through the
project_path it stamps into each graph file, copies it to the new slug and
records the mapping in aliases.json. The old directory under projects/ is
left in place; delete it yourself once you have checked the new one.
The user, project and maintenance graphs share one JSON structure:
{
"nodes": {
"node-id": {
"id": "node-id",
"gist": "Short description",
"notes": ["additional context"],
"touches": ["related/file.py"],
"_created_ts": 1706000000.0,
"_last_read_ts": 1706000000.0,
"_useful_ts": [1706000000.0],
"_archived": true
}
},
"edges": {
"source->target:relationship": {
"from": "source",
"to": "target",
"rel": "relationship",
"notes": ["edge context"]
}
},
"_meta": {
"versions": {
"node:node-id": {"v": 3, "ts": 1706000000.0, "session": "abc12345"}
},
"progress": {
"scout": {"last_ts": 1706000000, "sessions_reviewed": ["xyz"]}
},
"project_path": "/home/user/DevProj/my-app",
"namespace": {"kind": "project", "owner": null}
}
}- Fields that start with an underscore are the server's bookkeeping: creation
and last-read times, endorsement and credit times (
_useful_ts), the archived flag, the time a node was orphaned (_orphaned_ts) and a few more. The scorer reads them, so leave them alone when you edit a file by hand. -
_meta.versionstracks each node's and edge's change history, which lets the server refuse a write based on a stale view. -
_meta.progresskeeps task state across sessions (scout, extract, maintain, chore), including the trail of earlier maintenance passes. -
_meta.project_path(project graphs only) is how the server detects a renamed project folder.
An orphaned node that goes unrecalled for KG_ORPHAN_GRACE_DAYS (default 365)
is deleted permanently.
Creating, updating, deleting or promoting a node or edge saves that graph to
disk immediately. Read timestamps and maintenance changes are saved by a
background thread every KG_SAVE_INTERVAL seconds (default 30). A failed save
is logged and the graph stays marked as changed, so the next attempt retries
it. On a graceful shutdown (including kg stop) the server writes any graph
with unsaved changes first. An abrupt kill can lose changes that were not yet saved; the
atomic write below protects the previous file on disk, not unsaved in-memory
work.
Every graph save is atomic:
- Write the new content to
<file>.tmp. -
fsyncit, so the data is on disk. - Copy the current file to
.prev. - Rename the temp file over the real one (an atomic operation on POSIX).
If the process dies mid-write, the real file is untouched and a leftover
.tmp is overwritten by the next save.
Every save keeps one rolling copy of the previous good state beside the graph,
named by replacing the extension: user.prev for user.json, graph.prev
for a project's graph.json, maintain.prev for maintain.json. This covers
interrupted writes and corruption, not accidental deletion or longer-term
history. For those, set up versioned history.
If a graph file cannot be parsed when the server loads it, the server falls
back to the .prev copy on its own and logs Recovered graph from … .prev — primary file needs inspection. The next save writes the recovered graph back
to the .json file and copies the damaged file into the .prev slot. If you
want to inspect the damaged file, copy it out before that happens. If both
files fail to load, the server refuses to load that graph and names the file
in the error.
A node is meant to be stored as separate fields: a short gist headline plus
a notes list. Rarely, a client serializes the whole node (gist, notes and
surrounding tool-call markup) into the single gist string and leaves notes
empty. Because every full kg_read renders each active node's id and gist,
such an oversized gist can be many times its intended size and use up a large
share of the size budget.
The server repairs this in two places:
-
On write:
kg_put_nodesanitizes incoming data before storing it. - On load: when a graph is first read from disk, already-malformed nodes are healed and the repaired file is written back.
Healing splits out the real headline and moves the embedded notes and touches into their proper fields. It is idempotent (clean nodes are skipped) and never overwrites data you supplied: only fields left empty are filled in.
When it repairs something, the server log (kg logs) shows one line per node
and a summary:
WARNING - Healed corrupt node 'some-node-id': gist 2013->482 chars, recovered 4 note(s)
...
INFO - Healed 30 corrupt node(s) on load
This happens once per affected graph; the next load finds clean data and does nothing. Healing was introduced in 0.9.12.
Stop the server first, so a background save cannot overwrite the file you restore:
kg stop
# user graph
cp ~/.knowledge-graph/user.prev ~/.knowledge-graph/user.json
# a project graph
cp ~/.knowledge-graph/projects/<slug>/graph.prev \
~/.knowledge-graph/projects/<slug>/graph.json
kg startThe .prev file is exactly one save back, and saves are frequent: every
write, background maintenance, and the final save kg stop makes when a graph
has unsaved changes. It may therefore hold a more recent state than the one
you want. Look at it before you copy it; for an older state, use git. Do the
restore while no agent session is using memory, because kg mcp and the
session hooks start the server again on demand.
If you set up versioned history:
kg stop
cd ~/.knowledge-graph
git log --oneline -- user.json # find the commit you want
git checkout <commit> -- user.json # restore that version
kg startkg stop makes a commit of the current state first, so you can return to it
the same way.
If you cannot stop the server, copy the file into place and then make the running server re-read it from disk:
curl -s 'http://127.0.0.1:8765/api/graph/read?reload=true' # user graph
curl -s 'http://127.0.0.1:8765/api/graph/read?reload=true&project_path=<project-root>' # user graph and that project's graphThe reload lets the disk win and logs a warning if it drops unsaved in-memory changes. Do not restart the server instead: shutdown writes unsaved memory back to disk, which can overwrite the file you just restored.
If graph files are lost and no backup exists, /kg-scout can recover
insights from past Claude Code transcripts, Codex rollouts and Antigravity CLI
conversations. It reads history selectively and creates new nodes; it does not
reconstruct the previous graph or its metadata.
/kg-scout
Scout scans lightweight session metadata first and reads full sessions only where it finds signals of useful knowledge (decisions, corrections, recurring topics). See Skills Reference for details.
Claude Code stores full conversation transcripts as JSONL files in
~/.claude/projects/. They are the raw material /kg-scout mines, and the
archive that lets you trace a decision back to the session it came from.
By default, Claude Code deletes session files older than 30 days at startup.
The cleanupPeriodDays setting controls this. If you rely on /kg-scout to
fill gaps in your memory, a longer window gives it more to work with:
// ~/.claude/settings.json
{
"cleanupPeriodDays": 90
}You can also change it through /config in Claude Code. Session files can
grow to a few hundred MB over 90 days of active use; the graph itself stays
small.
Codex keeps its rollouts under ${CODEX_HOME:-~/.codex}/sessions/, and the
Antigravity CLI keeps its conversations under ~/.gemini/antigravity-cli/.
Their retention is set by those tools.
The .prev file holds one previous state. For longer history, make the
storage root a git repository. The server then commits changes on its own:
cd ~/.knowledge-graph
git init
printf '*.prev\n*.tmp\n' >> .gitignore
git add -A && git commit -m "initial"After this one-time setup:
- The server commits every 15 minutes, only when something changed
(
Auto-save YYYY-MM-DD HH:MMcommits).KG_AUTOCOMMIT_INTERVALsets the interval in seconds;0turns auto-commit off, including the shutdown commit. - It makes a final commit on graceful shutdown, after writing unsaved memory
to disk, and
kg stopcommits again after the server has stopped. -
kg commitforces a commit at any time. - The server checks for
.giton every tick, so agit inittakes effect without a restart.kg doctorreports whether the storage is versioned.
The commit stages everything in the directory that is not ignored, including
sessions.json and the JSONL logs. Changes made since the last commit can be
lost if the machine stops abruptly. Git history in the same directory is not
an off-machine backup.
Git suits this data because the graphs are small, human-readable JSON:
git log -p user.json shows what changed in your memory this week, and
recovering a single file is one git checkout.
Any file backup tool works on the directory (restic, rsync, your existing backup system). These add an independent copy; they do not replace git's readable diffs. Borg is a good fit for frequently changing data, because it deduplicates across archives:
borg init --encryption=none ~/.knowledge-graph-borgAdd to your crontab (crontab -e):
0 * * * * borg create --stats ~/.knowledge-graph-borg::'{now}' ~/.knowledge-graph
0 2 * * * borg prune ~/.knowledge-graph-borg --keep-hourly=24 --keep-daily=7 --keep-weekly=4
Point-in-time restore (stop the server first, as above):
borg extract ~/.knowledge-graph-borg::2026-05-17T03:00 --strip-components 3Graph files are plain JSON, and you can edit them with any text editor. For
live edits, prefer the Visual Editor or the kg_* tools: they keep
versions and references consistent.
To edit a file by hand, run kg stop, edit, then kg start (as with a
restore, do it while no session is using memory). Editing while the server
runs and then restarting can write the old in-memory state over your changes;
if you must edit with the server running, use the disk reload described under
Recovery.
Safe edits:
-
Delete a node: remove its entry from
nodes. Edges that point to a missing node are removed on the next load (cross-level edges to user nodes and edges to file paths are kept). - Edit a gist or notes: change the text directly.
-
Unarchive: delete the
_archivedkey from a node. The server may archive it again later if its score is low; reading it and endorsing it is what keeps it up.
Do not rename a node by editing its id: edges, version history and
references from other graphs would not follow. Use kg_rename_node.
The active part of each graph is bounded by the fixed size budget: 22,000 rendered characters per level (not configurable). When a level exceeds it, the lowest-scored active nodes are archived. Archived nodes stay on disk with their notes, and in a full read they cost only an anchor line. Graph files therefore grow with history rather than with the active set. The JSONL logs are capped as listed above, and orphaned nodes are deleted after the grace period.