Skip to content

Knowledge Graph API

Maxim Mironenko edited this page Jul 28, 2026 · 8 revisions

Knowledge Graph API

All tools are exposed via MCP and called by Claude Code automatically. You don't call these directly — Claude does, guided by the hidden skill descriptions loaded at session start.

Memory usually arrives without any tool call at all (0.9.17+): the SessionStart hook injects the rendered graph — including the session_id — into Claude's context before the first message. kg_read remains the fallback (server still warming up) and the API for node reads and re-reads.

Tools (9 total)

kg_read(cwd?, session_id?, id?, ids?, level?)

The primary entry point. Two modes:

Mode 1: Full graph read (no id/ids) Loads both user and project graphs: active nodes rendered in cluster order (related nodes together, hubs first) with their relationships indented beneath them, archived anchors, health stats, and a per-graph DEBT: line (maintenance urgency with its raw factors — see How It Works). Each edge appears once, under its first-rendered endpoint. Output is guaranteed to fit inline. The first call (with cwd) initializes a session and returns its session_id.

Mode 2: Node read (with id, or ids for a batch) Returns full content — gist + notes + touches + all of the node's edges (the crumbs to follow next) — as compact text. Archived/orphaned nodes are promoted to active. Batch several related nodes into one ids=[...] call instead of sequential single reads.

Parameters:

Param Type Required Description
cwd string First call only Project root directory. Initializes the session.
session_id string Later calls From the first read (or the preloaded block). Reuses the session instead of minting one per read.
id string No Node ID to read in full.
ids string[] No Several node IDs to read in one call (batch crumb-following).
level string No Hint: "user" or "project". If omitted, searches both.

When it's called: the loud full-graph read before substantive work (the preload is a compact core; this read renders the rest, showing preloaded gists as id-only anchors), and node reads throughout the session.


kg_search(query, session_id?)

Full-text search across node IDs, gists, notes, and touches in both user and project graphs. Reaches all three tiers — active, archived, and orphaned — and flags orphaned results so they can be promoted via a node read.

The query is tokenized on whitespace, and each token also contributes its ./_- subtokens ("claude.md-cleanup" → claude, md, cleanup plus the exact composite). Terms match through a light stem (schedule ≈ scheduling), adjacent subtokens form bigram terms with their own co-occurrence IDF (the pair "claude md" is strong evidence even where each half is common), and occurrences are field-weighted — a term in a node's id counts ×3, in its gist ×2, in notes/touches ×1, so a node about a concept outranks one that mentions it in passing. Per-term ranked lists merge via Reciprocal Rank Fusion (RRF, k=60) with sharpened IDF weighting. Results from the user and project graphs are unified into a single ranking.

Parameters:

Param Type Required Description
query string Yes One or more terms (case-insensitive). Multi-word queries rank best — corroborating terms and their bigrams sharpen the result far beyond running each word separately.
session_id string No Scopes the project search to this session's project and enables seen-dedup. Omitting it falls back to a best-effort search across all currently-loaded project graphs.

Returns (compact text, capped at 10K chars):

  • Top 5 hits with full treatment — notes included only for nodes the session hasn't already been shown (a seen hit renders as a one-line gist reminder; notes never re-dump — they stay one explicit node read away)
  • Connections between the hits — nodes on the shortest paths linking the top hits (id + gist) plus the path edges, so the results arrive with their relationships
  • Remaining matches as one-line id: gist entries

When to use: When a problem feels familiar, before asserting an assumption, whenever a mature graph plausibly covers the topic — in a long-lived graph the needed fact is often buried under fresher work, and finding it when it matters also feeds the usefulness signal that keeps it alive. (Duplicate checking before writes is no longer the caller's job — kg_put_node detects near-duplicates server-side.)


kg_put_node(session_id, level, id, gist, notes?, touches?)

Creates or updates a node. If the node exists, fields are merged. If the node was archived, it's automatically unarchived. Saves to disk immediately (write-through).

Parameters:

Param Type Required Description
session_id string Yes From kg_read
level "user" or "project" Yes Storage level
id string Yes Node ID (kebab-case)
gist string Yes Compressed headline — the core insight
notes string[] No Rationale, constraints, "why"
touches string[] No Related file paths or artifacts

Side effects: Triggers auto-compaction check. Broadcasts change to WebSocket clients.

Write-side nudges (v0.9.31): creating a new node probes its id + gist against its own graph through the search term pipeline. A near-duplicate (self-normalized similarity ratio ≥ 0.50) makes the tool result name the existing node and suggest folding into it; a hub mention (the gist re-describes an entity that ≥3 nodes hold and an undated node id owns) suggests an edge to the owner instead of re-describing — "keep this gist to what is NEW here." Both are one-line nudges; the write itself always proceeds.

Example:

kg_put_node(
  session_id="abc12345",
  level="project",
  id="api-rate-limiting",
  gist="Redis-backed sliding window; 429 response includes Retry-After header",
  touches=["src/middleware/rate_limit.py"],
  notes=["window size configurable via env var RATE_LIMIT_WINDOW"]
)

kg_put_edge(session_id, level, from, to, rel, notes?)

Creates or updates an edge. from and to can be node IDs or file paths — nodes for those IDs don't need to exist.

Parameters:

Param Type Required Description
session_id string Yes From kg_read
level "user" or "project" Yes Storage level
from string Yes Source node ID or file path
to string Yes Target node ID or file path
rel string Yes Relationship type (kebab-case)
notes string[] No Context about this relationship

Example:

kg_put_edge(
  session_id="abc12345",
  level="project",
  from="auth-module",
  to="src/config.yaml",
  rel="reads-config",
  notes=["JWT secret and token TTL"]
)

kg_delete_node(session_id, id)

Deletes a node and all edges connected to it. Automatically finds which graph (user or project) the node is in.

Returns: Node ID deleted, count of edges removed, and resolved level.


kg_delete_edge(session_id, from, to, rel)

Deletes a specific edge. All three identifiers (from, to, rel) must match exactly. Automatically finds which graph the edge is in.

Returns: {"deleted": true/false, "level": "..."}


kg_useful(session_id, ids)

Marks the nodes that actually helped this session — explicit usefulness endorsement that feeds archival scoring (useful knowledge stays active longer). Called toward the end of a session, judged against real results rather than mid-flight promise.

  • Budget: 5 likes per session, one vote per node per session.
  • Each like lands as a decaying timestamp on the node (90-day half-life).
  • Reads deliberately do not feed this signal — a well-formed gist never needs the full read, so read-counting would reward the weakest gists.
  • A like is not a content write: node versions, recency, and sync state are untouched.

Parameters:

Param Type Required Description
session_id string Yes From kg_read/preload
ids string[] Yes Node IDs that proved genuinely useful

Returns: accepted ids, per-id rejection reasons (already liked / budget exhausted / not found), and the remaining budget.


kg_sync(session_id)

Gets changes made by other sessions since this session's last sync. Returns diff of nodes and edges modified by other sessions.

Parameters:

Param Type Required Description
session_id string Yes From kg_read

Returns: Diff with user and project sections showing changed nodes/edges.

When to use: After subagents finish, before important decisions, periodically in long sessions (~30 min).


kg_progress(session_id, task_id, state?, level?)

Tracks multi-step task progress across context compaction and session boundaries. Omit state to read current progress; include state to write.

Parameters:

Param Type Required Description
session_id string Yes From kg_read
task_id string Yes e.g. "scout", "extract"
state object No Progress state to persist. Omit to read.
level string No Default: "user"

Example (write):

kg_progress(
  session_id="abc12345",
  task_id="scout",
  state={
    "last_ts": 1706000000,
    "sessions_reviewed": ["abc123"],
    "patterns_found": ["docker-networking"]
  }
)

Example (read):

kg_progress(session_id="abc12345", task_id="scout")

The task id "maintain" has a system meaning: a /kg-maintain pass stamps state.last_ts there when it completes, and that stamp is what resets the staleness factor of the graph's DEBT: line. Only stamped passes count.


Server Endpoints (hooks and dispatchers, not Claude)

Beyond the MCP tools, the server exposes REST endpoints that power the ambient behavior. Claude never calls these — the plugin's hooks and external dispatchers do:

Endpoint Caller Purpose
GET /api/session_bootstrap?project_path= SessionStart hook Registers a session and returns the compact-core preload (≤10K chars, injectable text + stats)
POST /api/prompt_context UserPromptSubmit hook Posts the raw hook payload; returns ready-to-print hook output — the full-read nudge, prompt-matched recall (≤3 unseen gists), or {} for "fall back to the reminder pools"
POST /api/tool_event PostToolUse hook (Read|WebFetch|WebSearch) Counts the target per project; returns a capture nudge only for an uncovered target re-derived across sessions (throttled)
GET /api/maintenance_debt Maintenance dispatchers Debt survey of every graph on disk, neediest first, project paths attached
GET /api/session_state?project_path= Legacy remind hook (pre-0.9.24 servers) Full-read flag only — kept for version skew
GET /health Anything Liveness + version

The contract is deliberate: hooks post raw stdin JSON and print whatever comes back — every decision (matching, thresholds, throttles, wording) lives server-side, so hook scripts stay trivial and can never break a session.

Edge and Node ID Conventions

  • Node IDs: kebab-case, descriptive, include domain hint. Good: auth-token-refresh. Bad: refresh.
  • Edge relationships: kebab-case verbs. Common: depends-on, requires, implements, configures, persists, calls, related-to.
  • Edges relate concepts; touches locate them. Prefer node→node edges; a file important enough to relate to several concepts graduates to a component node. Touches work best as precise pointers with a semantic anchor: config/prod.yaml:30-40 (upstream block) — the next session reads 10 lines instead of the whole file.
  • Cross-level edges are legitimate: a project node may point up to a user-level node (proj-decision --applies--> user-principle). Store such edges in the project graph.