Skip to content

Data and Backup

Claude edited this page Oct 9, 2026 · 15 revisions

Data and Backup

File Locations

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.

Project slugs

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.

Graph File Format

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.versions tracks each node's and edge's change history, which lets the server refuse a write based on a stale view.
  • _meta.progress keeps 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.

Write-Through Persistence

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.

Atomic Writes

Every graph save is atomic:

  1. Write the new content to <file>.tmp.
  2. fsync it, so the data is on disk.
  3. Copy the current file to .prev.
  4. 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.

Built-in Crash Protection

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.

Self-Healing on Load

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_node sanitizes 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.

Recovery

From the rolling .prev backup (one save back)

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 start

The .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.

From git history (any point in time)

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 start

kg stop makes a commit of the current state first, so you can return to it the same way.

Without stopping the server

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 graph

The 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.

Recovering from session history

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.

Session History Retention

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.

Versioned History (Recommended: Git)

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:MM commits). KG_AUTOCOMMIT_INTERVAL sets the interval in seconds; 0 turns auto-commit off, including the shutdown commit.
  • It makes a final commit on graceful shutdown, after writing unsaved memory to disk, and kg stop commits again after the server has stopped.
  • kg commit forces a commit at any time.
  • The server checks for .git on every tick, so a git init takes effect without a restart. kg doctor reports 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.

Off-machine backups

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-borg

Add 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 3

Manual Editing

Graph 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 _archived key 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.

Data Size

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.

Clone this wiki locally