Repository navigation
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 usually arrives without any tool call at all (0.9.17+): the SessionStart hook injects a compact core of the graph — including the
session_id— into the agent's context before the first message.kg_readremains the fallback (server still warming up) and the API for node reads and re-reads.
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.
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: gistentries
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.)
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" 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"]
)
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"]
)
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) |
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.
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": "..."}
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: 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.jsonlwith 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, 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.
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).
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-maintainpass stampsstate.last_tsthere when it completes, and that stamp is what resets the staleness factor of the graph'sDEBT:line. Only stamped passes count.
Beyond the MCP tools, the server exposes REST endpoints that power the ambient behavior. The agent 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 in Claude Code, ≤8K in Codex; 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/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.
-
Node IDs: kebab-case, 3–5 words naming the subject, no dates; the claim belongs in the gist. Good:
auth-token-refresh. Bad:refresh, ortoken-refresh-fails-silently-when-session-expired-2026-09. Change an id withkg_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.