Repository navigation
Knowledge Graph API
All tools are exposed over MCP and called by the agent (Claude Code, Codex, Antigravity or Claude Desktop). You don't call them directly; the agent does, guided by the tool descriptions and the hidden kg-core skill. This page is the reference for what each tool accepts and returns, checked against mcp_streamable_server.py.
Memory starts with a preload (0.9.17+). With the harness hooks enabled and trusted, SessionStart injects a compact core and a
session_id. The agent still makes one fullkg_read(session_id)before substantive work, then uses node reads for detail. Without a preload, the firstkg_readcreates the session.
Arguments are checked against each tool's input schema; a call that does not match is refused with Input validation error: .... Errors from the store (unknown node, unknown session, a refused id) come back as text starting with Error:.
The primary entry point. Two modes.
Mode 1: full graph read (no id/ids).
Renders user memory and, when the session has one, its project graph: active nodes in cluster order with each node's live edges beneath it, archived anchors, a HEALTH: line and a DEBT: line per graph. Each edge appears once. Gists the preload already delivered appear as id-only anchors. The render targets 50,000 characters; it can exceed that when active gists alone are larger, since active gists are never dropped. The first full read of a session ends with the instruction to announce "I have recalled KG Memories". A first call returns a session_id: with a project cwd it attaches that project's memory, without one it opens user memory only.
Mode 2: node read (id, or ids for a batch).
Returns the full content of each node, gist, notes, touches and all of the node's edges (the crumbs to follow next), as compact text. A node missing from the graph shows NOT FOUND with a pointer to kg_search. Reading an archived or orphaned node promotes it to active and brings its orphaned neighbours back to archived; the read also stamps the node's recency. A maintenance session's reads do neither. A batch has no combined size budget. Batch several related nodes into one ids=[...] call instead of sequential single reads.
Long replies. A reply longer than the client keeps whole arrives in parts cut at line ends: over 45,000 UTF-16 units in Claude Code (and Claude Desktop), over 36,000 bytes in Codex. Each part says it continues, and kg_read(session_id, more=true) returns the next. A node counts as seen, read or promoted only when the part showing it is delivered; a new read or a compaction drops undelivered parts. Antigravity cuts MCP results above about 10 KB, so there any tool reply over 3,500 bytes is queued and delivered by the next hook in chunks.
Parameters:
| Param | Type | Required | Description |
|---|---|---|---|
cwd |
string | No | Project root. On the first call it opens the session and loads the project graph; omitted, the session is user-only. With a user-only session_id, it attaches that project. A session bound to a project keeps it: a cwd outside that project is ignored, with a note. Project folders must be inside the user's home directory. |
session_id |
string | Later calls | From the preload or the first read. Reuses the session instead of opening a new one. An unknown session_id without cwd is an error. |
id |
string | No | Node id to read in full. |
ids |
string[] | No | Several node ids to read in one call. |
level |
user, project, maintain
|
No | For node reads, which graph to look in; omitted, the user graph is checked first, then the session's project. maintain without id/ids renders the maintenance memory instead of the graphs. A full read always renders user and project together. |
more |
bool | No |
true: the next part of a reply that said it continues. Pass session_id with it. |
maintenance |
bool | No | Only for a maintenance pass or chore: from this call on, the session's reads and writes do not count as use (no recency stamp, no promotion). |
When it's called: the full read before substantive work, and node reads throughout the session.
Full-text search across node ids, gists, notes and touches in the user graph and the session's project graph (never the maintain graph). It reaches all three tiers, active, archived and orphaned, and flags archived and orphaned results so they can be promoted with a node read.
The query is split on whitespace, and each token also contributes its ./_- parts ("claude.md-cleanup" gives claude, md, cleanup plus the whole token). Terms match through a light stem (schedule ≈ scheduling). Adjacent parts form two-word terms with their own rarity weight (the pair "claude md" is strong evidence even where each half is common). Occurrences are field-weighted: a term in a node's id counts ×3, in its gist ×2, in notes or touches ×1, so a node about a concept outranks one that mentions it in passing. Per-term ranked lists merge by Reciprocal Rank Fusion (k = 60), weighted by a sharpened IDF. User and project results share one ranking.
Parameters:
| Param | Type | Required | Description |
|---|---|---|---|
query |
string | Yes | One or more terms, case-insensitive. Several words usually rank better than one, because corroborating terms and their pairs sharpen the result. |
session_id |
string | No | Scopes the project search to the session's project and enables seen-deduplication. Without it, the search covers every project graph currently loaded, best-effort, and says so. |
Returns compact text, capped at 10,000 characters:
- the top 5 hits in full; notes are included only for nodes the session hasn't been shown yet (a hit the session has seen renders as a gist reminder, and its notes stay one node read away);
- the connections between the hits: nodes on the shortest paths linking the top hits (id + gist) and the path edges;
-
up to 10 further matches as one-line
id: gistentries.
When the trim is needed, further matches go first, then connections, then notes. Every node shown counts as seen for the session. The reply can end with up to three gists other sessions wrote meanwhile (see kg_sync).
When to use: when a problem feels familiar, before asserting an assumption, and before creating a node that may duplicate one. Search reaches below the preload and the full read. Being found is not an endorsement; a node you had to dig up that should have been on the surface is worth a kg_useful.
Creates or updates a node. On an existing node, 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. An archived or orphaned node that is written becomes active again. The graph is saved to disk at once.
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 answer 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 or the preload |
level |
user, project, maintain
|
Yes | Storage level. maintain is the maintenance agent's own memory; write there only from a maintenance chore or pass |
id |
string | Yes | Node id naming the subject: kebab-case, 3–5 words, no date. Letters, digits, ., _, -, up to 128 characters, starting with a letter or digit. A 6-word or dated id draws a nudge on any write; 7 or more words is refused on create (a date counts as one word) |
gist |
string | Yes | The lesson: the one claim this node makes about its subject. Soft target 300 characters; a longer gist is saved with a note to move detail into notes |
notes |
string[] | No | One case per note (what happened, where, what went wrong), plus rationale and constraints. Replaces the stored list |
touches |
string[] | No | Related file paths or artifact references, ideally precise (path:lines (anchor)). Replaces the stored list |
Side effects: a compaction check on the node's graph, a live update to connected visual editors, and the reply may end with up to three gists other sessions wrote meanwhile. Note credit: adding a note to a lesson another session wrote counts as this session's endorsement of it, once per node, and the reply says so.
Write-time hints (since 0.9.31). Creating a new node, in a graph with at least ten other nodes, compares its id and gist with the rest of its graph through the search pipeline. A near-duplicate (similarity ratio ≥ 0.50 against the new node's own best possible score) makes the reply name the existing node and suggest folding into it. Otherwise, a hub mention (the gist names an entity that at least max(6, 8% of the graph) nodes mention but no more than a quarter of them, and that an undated node's id names) suggests an edge to that node instead of re-describing it. Both are one-line hints; the write always goes through.
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. Endpoints are not checked when the edge is written, but a bare-id endpoint should name a node: in the same graph, or from a project graph, a user node. When a graph loads, edges whose bare-id endpoint matches no node are removed. An endpoint containing / or ~ counts as a file and is always kept.
An instance-of edge from a node created after its target is a repeat: the target lesson is credited like an endorsement, dated on the day the case was created.
Parameters:
| Param | Type | Required | Description |
|---|---|---|---|
session_id |
string | Yes | From kg_read or the preload |
level |
user, project, maintain
|
Yes | Storage level. Cross-level edges (a project node pointing to a user node) belong in the project graph |
from |
string | Yes | Source node id or file path (letters, digits, ., _, -, /, ~, up to 256 characters) |
to |
string | Yes | Target node id or file path |
rel |
string | Yes | Relationship type, kebab-case (letters, digits, ., _, -, up to 64 characters) |
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 drops all of that, and cross-level edges to the old id are removed 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 new id that already exists in the same graph is refused too, and so is one of seven or more words. If a project graph on disk cannot be rewritten, the reply names it so it can be checked by hand.
Use it when an id carries a claim instead of naming the subject, carries a date, or when the graph's own vocabulary has settled on another name. Ids matter: search weights them ×3 and prompt recall's evidence gate matches them.
Parameters:
| Param | Type | Required | Description |
|---|---|---|---|
session_id |
string | Yes | From kg_read or the 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 |
user, project, maintain
|
No | Found automatically when omitted (user graph first, then project); a maintain node must name its level |
Returns: the rename, its level, and how many edge references were rewired.
Deletes a node and every edge in its graph that touches it. The graph is found automatically: the user graph is checked first, then the session's project graph. There is no level argument, so if the same id exists at both levels, the user node is the one deleted, and maintain nodes cannot be deleted with this tool.
Returns: the deleted id, the number of edges removed and the graph's level.
Deletes one edge. All three of from, to and rel must match exactly. The graph is found automatically (user graph first, then the session's project graph).
Returns: Edge deleted: from->to:rel, or Edge not found: ....
Marks the nodes that earned their place. 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 and sinks when they stop. Two things earn it, both judged on what happened rather than on promise:
- Helped: the node was in front of the agent and the work went differently for it. Judged at wrap-up, against actual results.
- Missing: the node existed, the session needed it, and nothing surfaced it (the agent re-derived it, took a wrong turn it would have prevented, or the user had to supply it). Sent the moment the gap is established. Only a miss can correct a wrong archival decision; a hit confirms a right one. If the missing node was archived, read it too: the read promotes it, the endorsement keeps it from sinking again.
Rules:
- Five per session is the guidance, ten the hard cap; one vote per node per session. Past five, each reply says how far over the guidance the session is.
- 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. Refusals are logged too. - An endorsement adds a decaying timestamp to the node (
_useful_ts). Reads never feed this signal: a well-formed gist never needs the full read, so counting reads would reward the weakest gists. - An endorsement is not a content write: node versions and sync state are untouched. It does count as recent use (since 0.14.0).
- In a maintenance session it credits instead:
credits=1-3per node by conviction, at most 15 per pass, logged asvia: "maintenance"and recorded on the node (_credited_ts, shown when the node is read), so a later pass sees it was propped before. A credited orphan returns to the archive. A working session must leavecreditsout.
Parameters:
| Param | Type | Required | Description |
|---|---|---|---|
session_id |
string | Yes | From kg_read or the preload |
ids |
string[] | Yes | Node ids that helped, or that should have been surfaced and were not |
credits |
integer, 1–3 | No | Maintenance pass only: credits per node |
Returns: the accepted ids, a reason for each refused one (already liked this session, hard cap or pass total reached, not found, or credits used outside a maintenance session), and how many remain (before the hard cap, or in the pass).
Returns the node and edge changes other sessions made since this session's last sync (or its start, before the first sync), as a compact list with gists cut to 100 characters. The session's own writes are excluded. It is not a complete change log: deletions are not reported, and a rename shows only as a changed node under its new id. Re-read when those changes matter.
Most of the time a session does not need to call it: since 0.13.0, hook replies and kg_put_node/kg_search replies already carry up to three gists of other sessions' recent writes, each pushed once, with a pointer to kg_sync for the rest.
Parameters:
| Param | Type | Required | Description |
|---|---|---|---|
session_id |
string | Yes | From kg_read or the preload |
Returns: user and project sections listing changed nodes and edges, or No updates from other sessions.
When to use: after subagents finish, when resuming an earlier conversation, before a decision that depends on shared knowledge, and when a pushed notice says more changes are waiting.
Tracks multi-step task progress across context compaction and session boundaries. Omit state to read current progress; include it to write. Progress is stored in the graph file of the chosen level and saved at once.
Each write also appends a size-bounded copy of the state to a _trail ring (the last 20 writes), returned by the read. A later pass can see what earlier ones did and, when they recorded it, what they considered and declined, so a rejected change is not proposed again every pass. last_ts is set by the server's clock on every write; a value you send is replaced.
Parameters:
| Param | Type | Required | Description |
|---|---|---|---|
session_id |
string | Yes | From kg_read or the preload |
task_id |
string | Yes | e.g. "scout", "extract"
|
state |
object | No | Progress state to store. Omit to read |
level |
user, project, maintain
|
No | Default user
|
Example (write):
kg_progress(
session_id="abc12345",
task_id="scout",
state={
"sessions_reviewed": ["abc123"],
"patterns_found": ["docker-networking"]
}
)
Example (read):
kg_progress(session_id="abc12345", task_id="scout")
Two task ids have a system meaning. A
/kg-maintainpass stamps task"maintain"when it completes, and that stamp is what resets the staleness factor of the graph'sDEBT:line; only stamped passes count. Reading or writing task"maintain"or"chore"also flags the session as a maintenance session, so its reads and writes stop counting as use.
Beyond the MCP tools, the server exposes REST endpoints that power the ambient behaviour. Hooks, the visual editor, dispatchers and operations scripts use these:
| Endpoint | Caller | Purpose |
|---|---|---|
GET /api/session_bootstrap?project_path= |
SessionStart hook (Claude Code, Codex) | Registers or recovers the session and returns the compact-core preload, sized in the client's unit (9,500 UTF-16 units in Claude Code, 9,000 bytes in Codex); also takes claude_session_id, source and transcript_path for resume, fork, clear and compaction |
POST /api/prompt_context |
UserPromptSubmit hook | Posts the raw hook payload; returns ready-to-print hook output: the full-read reminder, prompt-matched recall (up to 4 hits), or {} for "fall back to the reminder pools". Budget notices and other sessions' writes ride along. A prompt is also what can dispatch a maintenance chore, when chores are on |
POST /api/tool_event |
PostToolUse hook (fires for every tool) | File recall for a file the memory names; otherwise counts read and fetch targets per project and returns a capture nudge only for an uncovered target re-derived across sessions (throttled). Budget notices and other sessions' writes ride along |
POST /api/antigravity/hook/{event}, POST /api/antigravity/ack
|
Antigravity hook script | The Antigravity adapter: the same decisions mapped to its hook events (preload limit 10,000 bytes), plus delivery and acknowledgement of queued large replies |
GET /api/graph/read?reload=true |
You, after restoring a .prev file |
Makes the server re-read the user graph from disk (add &project_path=<root> for that project's graph too); 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, with project paths |
GET /api/projects |
Visual editor | Lists stored project graphs and their recorded paths; read-only, independent of any harness's history |
GET /api/nodes/{level}/{node_id}/score |
Visual editor | Live score factors and comparison pool, without promoting the node or stamping a read |
POST /api/nodes/rename |
Editor / operations | Safe rename using the same store operation as kg_rename_node
|
GET /api/session_state?project_path= |
Prompt hooks from before 0.9.24 | Full-read flag only, kept so an old hook still works against a newer server |
GET /health, GET /api/health
|
Anything | Liveness and version |
The visual editor also uses create, update and delete endpoints for nodes, edges and progress (/api/nodes, /api/edges, /api/progress), not listed here.
The contract is deliberate: hooks post their raw JSON and print whatever comes back. Every decision (matching, thresholds, throttles, wording) lives in the server, so hook scripts stay trivial and cannot 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(too vague), ortoken-refresh-fails-silently-when-session-expired-2026-09(a claim and a date; refused on create). 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, andinstance-offor a case under the lesson it repeats. -
Edges relate concepts; touches locate them. Prefer node→node edges; a file important enough to relate to several concepts can become a component node. Touches work best as precise pointers with a short anchor:
config/prod.yaml:30-40 (upstream block), so the next session reads ten 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.