Skip to content

Knowledge Graph API

Maxim Mironenko edited this page Oct 8, 2026 · 15 revisions

Knowledge Graph API

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

Memory starts with a preload (0.9.17+): with trusted/enabled hooks, SessionStart injects a compact core and session_id. The agent must still make the loud full kg_read(session_id) before substantive work, then use node reads for detail. Without a preload, a first kg_read creates the session.

Tools (10 total)

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

The primary entry point. Two modes:

Mode 1: Full graph read (no id/ids) Loads user memory and the session's project graph when attached: active nodes in cluster order, live relationships, archived anchors, health and DEBT:. Each edge appears once. The full-graph render targets 50,000 characters; a reply longer than the client keeps whole arrives in parts (more=true for the next). Active gists are never dropped and can exceed that target; arbitrary full-node batches have no such combined budget. A first call returns a session_id: with a project cwd it attaches project memory, without one it opens user memory only.

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 No Project directory resolved within the user's home; may attach a project to a user-only session later. A project-bound session cannot be rebound to another project.
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 user, project, or explicitly maintain. Default reads user/project only; maintenance memory is isolated.
more bool No Next part of a reply that arrived in parts (Claude Code over 45,000 UTF-16 units, Codex over 36,000 bytes). A node counts as seen or read only when the part showing it goes out.
maintenance bool No A maintenance pass or chore: from this call on, the session's reads and writes do not count as use.

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, and before creating a node that may duplicate existing knowledge. Search reaches below the preload. The write-side duplicate nudge complements this check. Finding a node alone does not endorse it: kg_useful supplies that signal.


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

Creates or updates a node. If the node exists, fields you omit stay as they are, but notes and touches you send replace the stored lists: to add a note, read the node (kg_read(ids=[...])) and send the full list. If the node was archived, it's automatically unarchived. Saves to disk immediately (write-through).

Refused writes (since 0.10.1). A write built on a stale or partial view of an existing node is not applied: the node changed since this session last saw it (another session, a chore, or the visual editor wrote it), or the write would replace notes or touches this session has never read in their current form. The answer starts with NOT WRITTEN, gives the reason and shows the node as it stands; that counts as a full read, so merging your change into it and calling again goes through.

Parameters:

Param Type Required Description
session_id string Yes From kg_read
level user, project, maintain Yes Storage level; maintain is the maintenance agent's craft memory
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, project, maintain Yes Storage level; maintain is isolated from normal memory
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_rename_node(session_id, old_id, new_id, level?)

Renames a node and carries everything with it: every edge in every graph (including cross-level edges in project graphs that are not currently loaded, rewritten on disk), creation time, endorsements, archival state and version history. Sessions that already saw the node keep it as seen under its new id. This is the only safe way to change an id — kg_put_node under a new name plus kg_delete_node of the old one silently drops all of that, and the cross-level edges are deleted at the next load.

An edge endpoint is a bare id, resolved to the project's own node first and to the user node second, so the same id can exist at both levels. A rename that would make some edge reach a different node is refused (since 0.10.2): a user node cannot take an id that a project linking to it owns, and a project node cannot take the id of a user node its project's edges reach. The refusal names the projects involved; choose another id. A project node is referenced only from its own graph, so its rename rewrites nothing else.

Use it when an id carries a claim instead of naming the subject, carries a date, or when accumulated nodes reveal the vocabulary the graph actually uses. Ids are load-bearing: search weights them ×3 and recall matches them.

Parameters:

Param Type Required Description
session_id string Yes From kg_read/preload
old_id string Yes Current node ID
new_id string Yes New ID: the subject in 3–5 kebab-case words, no date
level string No user, project or maintain; auto-resolved when omitted (a maintain node must name its level)

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, credits?)

Marks the nodes that actually helped this session. An endorsement counts as usefulness and as recent use, and both fade (90-day half-life), so a node stays in view while sessions keep finding it useful. Called toward the end of a session, judged against real results rather than mid-flight promise.

  • Budget: about five per session as guidance, ten as a hard stop; one vote per node per session. Two kinds earn it: a node that helped (judged at wrap-up) and a node that was missing when it was needed (sent the moment the gap shows) — only a miss can correct a wrong archival.
  • Each endorsement is logged to useful.jsonl with the route by which the node reached the session (preload, full read, prompt or file recall, search, read by id), so surfaced and dug-up knowledge can be told apart.
  • 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 and sync state are untouched. It does refresh recency (since 0.14.0).
  • In a maintenance session it credits instead: credits=1-3 per node by conviction, 15 per pass, logged as via: "maintenance" and recorded on the node, so a later pass sees it was propped before. A credited orphan returns to the archive.

Parameters:

Param Type Required Description
session_id string Yes From kg_read/preload
ids string[] Yes Node IDs that proved genuinely useful
credits integer No Maintenance pass only: 1–3 per node

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


kg_sync(session_id)

Gets surviving node/edge updates made by other sessions since the last sync (or session start before the first sync). It is not a complete change log: deletions and renames are not reported as tombstones. Re-read for a current graph view when those changes matter.

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 user, project, maintain; 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, editor and operations)

Beyond the MCP tools, the server exposes REST endpoints that power the ambient behavior. Hooks, the visual editor, dispatchers and operations scripts use these:

Endpoint Caller Purpose
GET /api/session_bootstrap?project_path= SessionStart hook Registers a session and returns the compact-core preload (sized in each client's unit: 9,500 UTF-16 units in Claude Code, 9,000 bytes in Codex, 10,000 bytes in Antigravity; 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 (≤4 hits), or {} for "fall back to the reminder pools". A prompt is also what can dispatch a maintenance chore, when chores are on
POST /api/tool_event PostToolUse hook (file tools, shell, apply_patch, web) File recall for a file the memory names; otherwise counts the target per project and returns a capture nudge only for an uncovered target re-derived across sessions (throttled)
GET /api/graph/read?reload=true You, after restoring a .prev file Makes the server re-read the graph files from disk; disk wins, and any unsaved in-memory change is logged as dropped
GET /api/maintenance_debt Maintenance dispatchers Debt survey of every graph on disk, neediest first, project paths attached
GET /api/projects Visual editor Lists stored project graphs and recorded paths; read-only, independent of harness history
GET /api/nodes/{level}/{node_id}/score Visual editor Live score factors and comparison pool without promoting or stamping a read
POST /api/nodes/rename Editor/operations API Safe rename using the same store operation as kg_rename_node
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.

Every endpoint refuses a request whose Host is not local (421) and, since 0.9.44, any cross-site browser request (Sec-Fetch-Site: cross-site or a non-local Origin, 403). Non-browser clients are unaffected.

Edge and Node ID Conventions

  • Node IDs: kebab-case, 3–5 words naming the subject, no dates; the claim belongs in the gist. Good: auth-token-refresh. Bad: refresh, or token-refresh-fails-silently-when-session-expired-2026-09. Change an id with kg_rename_node, never by put + delete.
  • 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.

Clone this wiki locally