memgraph-context-graph 0.7.4__tar.gz → 0.9.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/PKG-INFO +77 -11
  2. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/README.md +76 -10
  3. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/docs/command-hooks.md +14 -13
  4. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/pyproject.toml +1 -1
  5. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/adapters/_identity.py +52 -5
  6. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/adapters/_opencode_plugin.js +36 -0
  7. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/adapters/_spec.py +26 -1
  8. memgraph_context_graph-0.9.0/src/memgraph_context_graph/adapters/agent_mcp.py +326 -0
  9. memgraph_context_graph-0.9.0/src/memgraph_context_graph/adapters/agent_memory.py +201 -0
  10. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/adapters/antigravity_cli.py +77 -17
  11. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/adapters/claude_code.py +2 -0
  12. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/adapters/codex.py +2 -0
  13. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/adapters/copilot_cli.py +6 -1
  14. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/adapters/cursor.py +6 -1
  15. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/adapters/grok.py +2 -1
  16. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/adapters/opencode.py +5 -1
  17. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/cli.py +89 -78
  18. memgraph_context_graph-0.9.0/src/memgraph_context_graph/connectors.py +71 -0
  19. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/hooks/cli.py +6 -3
  20. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/hooks/runner.py +46 -121
  21. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/mcp_server.py +20 -6
  22. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/setup/agents.py +82 -3
  23. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/setup/cli.py +41 -2
  24. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/setup/engine.py +31 -10
  25. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/setup/prompts.py +4 -8
  26. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/setup/tool.py +29 -12
  27. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/setup/uninstall.py +11 -5
  28. memgraph_context_graph-0.9.0/src/memgraph_context_graph/setup/update.py +124 -0
  29. memgraph_context_graph-0.9.0/src/memgraph_context_graph/terminal.py +38 -0
  30. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/tests/conftest.py +24 -0
  31. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/tests/fake_agent_cli.py +6 -1
  32. memgraph_context_graph-0.9.0/tests/test_agent_memory_wiring.py +215 -0
  33. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/tests/test_antigravity_cli_adapter.py +46 -7
  34. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/tests/test_capture_log.py +5 -2
  35. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/tests/test_config_cli.py +22 -0
  36. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/tests/test_hook_cli.py +37 -4
  37. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/tests/test_identity.py +32 -0
  38. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/tests/test_setup.py +120 -8
  39. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/tests/test_setup_docker.py +3 -2
  40. memgraph_context_graph-0.9.0/tests/test_terminal.py +35 -0
  41. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/tests/test_tools.py +2 -1
  42. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/.gitignore +0 -0
  43. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/CONTEXT.md +0 -0
  44. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/LICENSE +0 -0
  45. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/docs/adr/0001-runtime-adapter-terminology.md +0 -0
  46. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/docs/adr/0002-config-file-only-hook-resolution.md +0 -0
  47. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/docs/adr/0003-config-path-override.md +0 -0
  48. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/docs/adr/0004-user-id-in-session-start-event.md +0 -0
  49. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/docs/adr/0005-config-file-covers-llm-key-and-subprocess-env.md +0 -0
  50. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/docs/adr/0006-memory-tool-writes-explicit-memory.md +0 -0
  51. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/docs/research/2026-10-codex-memory.md +0 -0
  52. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/__init__.py +0 -0
  53. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/adapters/__init__.py +0 -0
  54. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/adapters/claude.py +0 -0
  55. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/adapters/claude_code_memory.py +0 -0
  56. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/adapters/codex_memory.py +0 -0
  57. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/adapters/openai.py +0 -0
  58. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/capture_log.py +0 -0
  59. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/events.py +0 -0
  60. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/hooks/__init__.py +0 -0
  61. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/hooks/runtime_plugin.py +0 -0
  62. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/link.py +0 -0
  63. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/memory_backend.py +0 -0
  64. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/protocols.py +0 -0
  65. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/setup/__init__.py +0 -0
  66. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/setup/memgraph.py +0 -0
  67. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/spaces.py +0 -0
  68. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/src/memgraph_context_graph/tools.py +0 -0
  69. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/tests/__init__.py +0 -0
  70. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/tests/test_claude_code_adapter.py +0 -0
  71. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/tests/test_codex_adapter.py +0 -0
  72. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/tests/test_context_before_tool.py +0 -0
  73. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/tests/test_copilot_cli_adapter.py +0 -0
  74. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/tests/test_cursor_adapter.py +0 -0
  75. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/tests/test_dev_scripts.py +0 -0
  76. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/tests/test_events.py +0 -0
  77. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/tests/test_grok_adapter.py +0 -0
  78. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/tests/test_link.py +0 -0
  79. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/tests/test_opencode_adapter.py +0 -0
  80. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/tests/test_plugin_hooks.py +0 -0
  81. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/tests/test_runner_sessions_graph_connector.py +0 -0
  82. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/tests/test_runtime_plugin.py +0 -0
  83. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.9.0}/tests/test_spaces.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: memgraph-context-graph
3
- Version: 0.7.4
3
+ Version: 0.9.0
4
4
  Summary: Connect agent SDKs to context-graph components (actions-graph, skills-graph, etc.)
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -202,6 +202,7 @@ memgraph-context-graph setup # interactive on a terminal; pl
202
202
  memgraph-context-graph setup claude-code codex # just these agents
203
203
  memgraph-context-graph setup cursor --project-dir ~/work/billing
204
204
  memgraph-context-graph setup --plan # print the plan, change nothing
205
+ memgraph-context-graph update [runtime ...] # upgrade the package, refresh the wiring and the schema
205
206
  memgraph-context-graph uninstall [runtime ...] # remove the wiring; the graph and config stay
206
207
  ```
207
208
 
@@ -211,6 +212,15 @@ wires each agent, and runs `doctor`'s checks. Every step checks before it acts,
211
212
  missing. The TUI starts only when stdin and stdout are both terminals; the plugins' `scripts/bootstrap.sh`
212
213
  runs `setup <runtime> --no-tui`.
213
214
 
215
+ **Updating.** Upgrading the package moves the code hooks and the MCP server run, but what setup wrote once stays
216
+ as it was. `update` upgrades the uv tool and its connectors, then, with the new release, refreshes everything
217
+ that is wired: Claude Code's and Codex's plugin (marketplace refreshed, plugin updated), each per-project agent's
218
+ hook file in `--project-dir` (default: the current directory) and its MCP registration, and the graph's indexes
219
+ and constraints. It then runs `doctor`'s checks. Name runtimes to refresh only those; `--no-upgrade` skips the
220
+ package step. Your own hooks, the config and the graph's data are untouched, and running it again changes nothing.
221
+ Running agent sessions keep the hooks they loaded: restart them. Outside the uv tool (a virtualenv, say) the
222
+ package step only warns, and you upgrade it yourself first.
223
+
214
224
  **Identity and connection.** setup writes `~/.config/context-graph/config.toml`; hooks read their configuration from that file at runtime (see [Configuration](#configuration) — env vars are **not** read at hook time). To change your identity later:
215
225
 
216
226
  ```bash
@@ -268,6 +278,10 @@ database = "memgraph"
268
278
  [llm]
269
279
  openai_api_key = ""
270
280
  anthropic_api_key = ""
281
+ derive_model = "claude-sonnet-5-5"
282
+
283
+ [capture]
284
+ connectors = "skills-graph, actions-graph, sessions-graph"
271
285
 
272
286
  [reconcile]
273
287
  auto_reconcile = true
@@ -275,12 +289,21 @@ auto_reconcile = true
275
289
  [recall]
276
290
  embedding_model = "BAAI/bge-small-en-v1.5"
277
291
  turns_k = "8"
292
+
293
+ [extraction]
294
+ model = "fastino/gliner2.5-base-v1"
295
+ entity_threshold = "0.3"
278
296
  ```
279
297
 
280
298
  `[reconcile]` turns on sessions-graph's auto-trigger reconciliation (see
281
299
  [sessions-graph § reconciliation](../sessions-graph/README.md#session-reconciliation)),
282
300
  which needs no LLM. `[llm]` is optional: a key only lets reconciliation learn
283
- the user's own ontology (`sessions-graph derive`).
301
+ the user's own ontology (`sessions-graph derive`), and `derive_model` picks
302
+ the model it calls (default `claude-sonnet-5-5` with Anthropic, `gpt-5` with OpenAI).
303
+ `[capture] connectors` is which graph connectors a hook enables when its
304
+ command names no `--connector`, from `skills-graph`, `actions-graph`,
305
+ `sessions-graph` and `resources-graph`; unset means the first three, and
306
+ an empty value enables none. Setup installs the ones it names.
284
307
  `[reconcile]` is omitted entirely from a file setup wrote — absent
285
308
  means "never configured," distinct from an explicit `auto_reconcile = false`.
286
309
  `[recall]` is optional too: without it, recall runs with the widths the
@@ -288,6 +311,10 @@ benchmark measured; see [Recall](#recall-memory-for-the-harnesss-model).
288
311
  `[ontology]` is optional as well: `path` names a schema file the user's
289
312
  sessions are extracted under and `derive` is `extend` (the default) or
290
313
  `off`; see sessions-graph's [Ontology versions](../sessions-graph/README.md#ontology-versions).
314
+ `[extraction]` tunes how reconciliation, re-extraction and derivation read
315
+ sessions, all optional: `model` (a GLiNER2 checkpoint), `entity_threshold` and
316
+ `relation_threshold` (drop extractions below that confidence, 0 to 1), and
317
+ `chunk_size`/`chunk_overlap` (words per window); unset keys keep GLiNER2's defaults.
291
318
 
292
319
  Manage it with:
293
320
 
@@ -296,8 +323,9 @@ memgraph-context-graph config show
296
323
  memgraph-context-graph config get memgraph.url
297
324
  memgraph-context-graph config set <key> <value>
298
325
  # keys: identity.user_id, memgraph.{url,user,password,database},
299
- # llm.{openai_api_key,anthropic_api_key}, reconcile.auto_reconcile,
300
- # recall.embedding_model, ontology.{path,derive}, memory.backend
326
+ # llm.{openai_api_key,anthropic_api_key,derive_model}, capture.connectors,
327
+ # reconcile.auto_reconcile, recall.embedding_model, ontology.{path,derive}, memory.backend
328
+ # [recall] widths and [extraction] are edited in the file.
301
329
  ```
302
330
 
303
331
  Hooks never read environment variables for these values, and neither does setup: it writes only
@@ -308,15 +336,23 @@ Hooks never read environment variables for these values, and neither does setup:
308
336
 
309
337
  The hooks write sessions into Memgraph; recall reads them back. With
310
338
  sessions-graph installed, `memgraph-context-graph mcp` serves a `recall(question)`
311
- tool over stdio MCP, and the Codex and Claude Code plugins bundle that server,
312
- so installing a plugin is all it takes. The tool returns context, not an answer:
339
+ tool over stdio MCP. The Codex and Claude Code plugins bundle that server, so
340
+ installing a plugin is all it takes; for the other agents, `setup` registers it
341
+ in each one's own user-level MCP config (see
342
+ [every agent](#every-agent-registration-switch-and-session-start)). The tool returns context, not an answer:
313
343
  the dated messages and facts from the user's own sessions that match the
314
344
  question, behind a header with the rules for reading them. The harness's model
315
345
  answers from those rows.
316
346
 
317
- At session start the hook adds one line to the model's context saying the tool
318
- exists. It never pushes retrieved content; the model calls `recall` when a
319
- question needs memory.
347
+ At session start the hook adds a line to the model's context saying the tool
348
+ exists, then where the user left off in this project: the last session's date,
349
+ agent, first and last ask and last reply, and the first ask of the two before
350
+ it, quoted from captured turns. Sessions from any agent count, so a session in
351
+ Grok picks up where one in Codex stopped. It is a pointer, not a recall result:
352
+ nothing is ranked against a question, and the model still calls `recall` when
353
+ one needs memory. A session's project is its checkout's key (the same one
354
+ memory folders use), recorded when it starts. Turn it off with
355
+ `memgraph-context-graph config set session_start.resume false`.
320
356
 
321
357
  A connector may also add a line just before a tool runs
322
358
  (`GraphConnector.context_before_tool`) — resources-graph's Nudge that what the
@@ -347,6 +383,7 @@ from the tool call, so a model can only read its own user's sessions.
347
383
  | `facts_k`, `fact_turns_k` | `15`, `8` | Facts matched, and the messages they were read from |
348
384
  | `user_fact_types`, `user_facts_k` | `2`, `30` | Relation types gathered whole, for counting questions |
349
385
  | `turn_chars` | `1500` | Characters shown per message |
386
+ | `query_instruction` | per model | Prefix embedded with the question; bge's English models get their retrieval instruction, other models none |
350
387
 
351
388
  Recall needs Memgraph with MAGE (`memgraph/memgraph-mage`) for its vector lanes;
352
389
  without it, it answers from text search and says so. `doctor` checks both
@@ -359,8 +396,9 @@ was benchmarked in; the JSON is what `--json` prints.
359
396
 
360
397
  ### Memory: Context Graph as the harness's memory
361
398
 
362
- Claude Code and Codex each keep their own memory. Context Graph can replace it
363
- for a user who opts in:
399
+ Every agent `setup` knows can use Context Graph as its memory. Claude Code and
400
+ Codex each keep their own memory, and Context Graph replaces it for a user who
401
+ opts in:
364
402
 
365
403
  ```bash
366
404
  memgraph-context-graph setup claude-code --memory-backend context-graph
@@ -431,6 +469,34 @@ name is used. See
431
469
  [ADR 0006](docs/adr/0006-memory-tool-writes-explicit-memory.md) for why this is
432
470
  the one write the model makes over MCP.
433
471
 
472
+ #### Every agent: registration, switch and session start
473
+
474
+ The other five have no plugin, so `setup <runtime>` registers the
475
+ `context-graph` MCP server in the agent's own user-level config: memory belongs
476
+ to the user, not to one project. It adds the server's tools to the agent's
477
+ persistent allow-list where it keeps one, because a headless run refuses a tool
478
+ that needs approval. `--memory-backend context-graph` then turns the agent's
479
+ own memory off, and `uninstall` undoes both. Every edited file keeps the rest of
480
+ its content and is backed up once to `<name>.context-graph.bak`.
481
+
482
+ | Agent | Built-in memory, and the switch | Server registered in | Session-start text reaches the model through |
483
+ |---|---|---|---|
484
+ | Claude Code | auto memory; `autoMemoryEnabled: false` | the plugin | `SessionStart` `additionalContext` |
485
+ | Codex | memories; three keys in `config.toml` | the plugin | `SessionStart` `additionalContext` (~2,500-token cap) |
486
+ | Copilot CLI | Copilot Memory (on GitHub, not importable); `"memory": false` in `~/.copilot/settings.json` | `~/.copilot/mcp-config.json` (no allow-list: approve once, or `--allow-tool=context-graph`) | `sessionStart` top-level `additionalContext` |
487
+ | Cursor | none (Memories was removed) | `~/.cursor/mcp.json`; allow `Mcp(context-graph:*)` in `~/.cursor/cli-config.json` | `sessionStart` `additional_context` (the IDE's agent window can drop it; untested here) |
488
+ | OpenCode | none | `mcp.servers` in `~/.config/opencode/opencode.json`, `codemode: false`; an allow rule in `permissions` | the plugin's `context` hook, added to the system prompt of every request |
489
+ | Antigravity CLI | none (Knowledge is off in the CLI) | `~/.gemini/config/mcp_config.json`; allow `mcp(context-graph/*)` in `~/.gemini/antigravity-cli/settings.json` | `SessionStart` `injectSteps` system message |
490
+ | Grok Build | topic notes under `~/.grok/memory-v2/`, imported to `/memories/grok/`; `[memory_v2] enabled = false` | `~/.grok/config.toml`; allow `MCPTool(context-graph__*)` | the MCP server's `instructions` (`mcp --session-context`): Grok's start hooks can't reach the model |
491
+
492
+ Antigravity's hooks carry no prompt or reply text, so its turns are read from
493
+ the transcript its `Stop` payload points at; that is what gives its sessions a
494
+ place in recall and in the next session's "where you left off".
495
+
496
+ `live-hooks-e2e.sh memory` checks this against each installed agent: a memory
497
+ written, one read back in a new session, and the next session's index and last
498
+ ask.
499
+
434
500
  ### Codex and Claude Code Plugins
435
501
 
436
502
  Both runtimes have a distributable Runtime Plugin that wires hooks and the MCP server; marketplace
@@ -177,6 +177,7 @@ memgraph-context-graph setup # interactive on a terminal; pl
177
177
  memgraph-context-graph setup claude-code codex # just these agents
178
178
  memgraph-context-graph setup cursor --project-dir ~/work/billing
179
179
  memgraph-context-graph setup --plan # print the plan, change nothing
180
+ memgraph-context-graph update [runtime ...] # upgrade the package, refresh the wiring and the schema
180
181
  memgraph-context-graph uninstall [runtime ...] # remove the wiring; the graph and config stay
181
182
  ```
182
183
 
@@ -186,6 +187,15 @@ wires each agent, and runs `doctor`'s checks. Every step checks before it acts,
186
187
  missing. The TUI starts only when stdin and stdout are both terminals; the plugins' `scripts/bootstrap.sh`
187
188
  runs `setup <runtime> --no-tui`.
188
189
 
190
+ **Updating.** Upgrading the package moves the code hooks and the MCP server run, but what setup wrote once stays
191
+ as it was. `update` upgrades the uv tool and its connectors, then, with the new release, refreshes everything
192
+ that is wired: Claude Code's and Codex's plugin (marketplace refreshed, plugin updated), each per-project agent's
193
+ hook file in `--project-dir` (default: the current directory) and its MCP registration, and the graph's indexes
194
+ and constraints. It then runs `doctor`'s checks. Name runtimes to refresh only those; `--no-upgrade` skips the
195
+ package step. Your own hooks, the config and the graph's data are untouched, and running it again changes nothing.
196
+ Running agent sessions keep the hooks they loaded: restart them. Outside the uv tool (a virtualenv, say) the
197
+ package step only warns, and you upgrade it yourself first.
198
+
189
199
  **Identity and connection.** setup writes `~/.config/context-graph/config.toml`; hooks read their configuration from that file at runtime (see [Configuration](#configuration) — env vars are **not** read at hook time). To change your identity later:
190
200
 
191
201
  ```bash
@@ -243,6 +253,10 @@ database = "memgraph"
243
253
  [llm]
244
254
  openai_api_key = ""
245
255
  anthropic_api_key = ""
256
+ derive_model = "claude-sonnet-5-5"
257
+
258
+ [capture]
259
+ connectors = "skills-graph, actions-graph, sessions-graph"
246
260
 
247
261
  [reconcile]
248
262
  auto_reconcile = true
@@ -250,12 +264,21 @@ auto_reconcile = true
250
264
  [recall]
251
265
  embedding_model = "BAAI/bge-small-en-v1.5"
252
266
  turns_k = "8"
267
+
268
+ [extraction]
269
+ model = "fastino/gliner2.5-base-v1"
270
+ entity_threshold = "0.3"
253
271
  ```
254
272
 
255
273
  `[reconcile]` turns on sessions-graph's auto-trigger reconciliation (see
256
274
  [sessions-graph § reconciliation](../sessions-graph/README.md#session-reconciliation)),
257
275
  which needs no LLM. `[llm]` is optional: a key only lets reconciliation learn
258
- the user's own ontology (`sessions-graph derive`).
276
+ the user's own ontology (`sessions-graph derive`), and `derive_model` picks
277
+ the model it calls (default `claude-sonnet-5-5` with Anthropic, `gpt-5` with OpenAI).
278
+ `[capture] connectors` is which graph connectors a hook enables when its
279
+ command names no `--connector`, from `skills-graph`, `actions-graph`,
280
+ `sessions-graph` and `resources-graph`; unset means the first three, and
281
+ an empty value enables none. Setup installs the ones it names.
259
282
  `[reconcile]` is omitted entirely from a file setup wrote — absent
260
283
  means "never configured," distinct from an explicit `auto_reconcile = false`.
261
284
  `[recall]` is optional too: without it, recall runs with the widths the
@@ -263,6 +286,10 @@ benchmark measured; see [Recall](#recall-memory-for-the-harnesss-model).
263
286
  `[ontology]` is optional as well: `path` names a schema file the user's
264
287
  sessions are extracted under and `derive` is `extend` (the default) or
265
288
  `off`; see sessions-graph's [Ontology versions](../sessions-graph/README.md#ontology-versions).
289
+ `[extraction]` tunes how reconciliation, re-extraction and derivation read
290
+ sessions, all optional: `model` (a GLiNER2 checkpoint), `entity_threshold` and
291
+ `relation_threshold` (drop extractions below that confidence, 0 to 1), and
292
+ `chunk_size`/`chunk_overlap` (words per window); unset keys keep GLiNER2's defaults.
266
293
 
267
294
  Manage it with:
268
295
 
@@ -271,8 +298,9 @@ memgraph-context-graph config show
271
298
  memgraph-context-graph config get memgraph.url
272
299
  memgraph-context-graph config set <key> <value>
273
300
  # keys: identity.user_id, memgraph.{url,user,password,database},
274
- # llm.{openai_api_key,anthropic_api_key}, reconcile.auto_reconcile,
275
- # recall.embedding_model, ontology.{path,derive}, memory.backend
301
+ # llm.{openai_api_key,anthropic_api_key,derive_model}, capture.connectors,
302
+ # reconcile.auto_reconcile, recall.embedding_model, ontology.{path,derive}, memory.backend
303
+ # [recall] widths and [extraction] are edited in the file.
276
304
  ```
277
305
 
278
306
  Hooks never read environment variables for these values, and neither does setup: it writes only
@@ -283,15 +311,23 @@ Hooks never read environment variables for these values, and neither does setup:
283
311
 
284
312
  The hooks write sessions into Memgraph; recall reads them back. With
285
313
  sessions-graph installed, `memgraph-context-graph mcp` serves a `recall(question)`
286
- tool over stdio MCP, and the Codex and Claude Code plugins bundle that server,
287
- so installing a plugin is all it takes. The tool returns context, not an answer:
314
+ tool over stdio MCP. The Codex and Claude Code plugins bundle that server, so
315
+ installing a plugin is all it takes; for the other agents, `setup` registers it
316
+ in each one's own user-level MCP config (see
317
+ [every agent](#every-agent-registration-switch-and-session-start)). The tool returns context, not an answer:
288
318
  the dated messages and facts from the user's own sessions that match the
289
319
  question, behind a header with the rules for reading them. The harness's model
290
320
  answers from those rows.
291
321
 
292
- At session start the hook adds one line to the model's context saying the tool
293
- exists. It never pushes retrieved content; the model calls `recall` when a
294
- question needs memory.
322
+ At session start the hook adds a line to the model's context saying the tool
323
+ exists, then where the user left off in this project: the last session's date,
324
+ agent, first and last ask and last reply, and the first ask of the two before
325
+ it, quoted from captured turns. Sessions from any agent count, so a session in
326
+ Grok picks up where one in Codex stopped. It is a pointer, not a recall result:
327
+ nothing is ranked against a question, and the model still calls `recall` when
328
+ one needs memory. A session's project is its checkout's key (the same one
329
+ memory folders use), recorded when it starts. Turn it off with
330
+ `memgraph-context-graph config set session_start.resume false`.
295
331
 
296
332
  A connector may also add a line just before a tool runs
297
333
  (`GraphConnector.context_before_tool`) — resources-graph's Nudge that what the
@@ -322,6 +358,7 @@ from the tool call, so a model can only read its own user's sessions.
322
358
  | `facts_k`, `fact_turns_k` | `15`, `8` | Facts matched, and the messages they were read from |
323
359
  | `user_fact_types`, `user_facts_k` | `2`, `30` | Relation types gathered whole, for counting questions |
324
360
  | `turn_chars` | `1500` | Characters shown per message |
361
+ | `query_instruction` | per model | Prefix embedded with the question; bge's English models get their retrieval instruction, other models none |
325
362
 
326
363
  Recall needs Memgraph with MAGE (`memgraph/memgraph-mage`) for its vector lanes;
327
364
  without it, it answers from text search and says so. `doctor` checks both
@@ -334,8 +371,9 @@ was benchmarked in; the JSON is what `--json` prints.
334
371
 
335
372
  ### Memory: Context Graph as the harness's memory
336
373
 
337
- Claude Code and Codex each keep their own memory. Context Graph can replace it
338
- for a user who opts in:
374
+ Every agent `setup` knows can use Context Graph as its memory. Claude Code and
375
+ Codex each keep their own memory, and Context Graph replaces it for a user who
376
+ opts in:
339
377
 
340
378
  ```bash
341
379
  memgraph-context-graph setup claude-code --memory-backend context-graph
@@ -406,6 +444,34 @@ name is used. See
406
444
  [ADR 0006](docs/adr/0006-memory-tool-writes-explicit-memory.md) for why this is
407
445
  the one write the model makes over MCP.
408
446
 
447
+ #### Every agent: registration, switch and session start
448
+
449
+ The other five have no plugin, so `setup <runtime>` registers the
450
+ `context-graph` MCP server in the agent's own user-level config: memory belongs
451
+ to the user, not to one project. It adds the server's tools to the agent's
452
+ persistent allow-list where it keeps one, because a headless run refuses a tool
453
+ that needs approval. `--memory-backend context-graph` then turns the agent's
454
+ own memory off, and `uninstall` undoes both. Every edited file keeps the rest of
455
+ its content and is backed up once to `<name>.context-graph.bak`.
456
+
457
+ | Agent | Built-in memory, and the switch | Server registered in | Session-start text reaches the model through |
458
+ |---|---|---|---|
459
+ | Claude Code | auto memory; `autoMemoryEnabled: false` | the plugin | `SessionStart` `additionalContext` |
460
+ | Codex | memories; three keys in `config.toml` | the plugin | `SessionStart` `additionalContext` (~2,500-token cap) |
461
+ | Copilot CLI | Copilot Memory (on GitHub, not importable); `"memory": false` in `~/.copilot/settings.json` | `~/.copilot/mcp-config.json` (no allow-list: approve once, or `--allow-tool=context-graph`) | `sessionStart` top-level `additionalContext` |
462
+ | Cursor | none (Memories was removed) | `~/.cursor/mcp.json`; allow `Mcp(context-graph:*)` in `~/.cursor/cli-config.json` | `sessionStart` `additional_context` (the IDE's agent window can drop it; untested here) |
463
+ | OpenCode | none | `mcp.servers` in `~/.config/opencode/opencode.json`, `codemode: false`; an allow rule in `permissions` | the plugin's `context` hook, added to the system prompt of every request |
464
+ | Antigravity CLI | none (Knowledge is off in the CLI) | `~/.gemini/config/mcp_config.json`; allow `mcp(context-graph/*)` in `~/.gemini/antigravity-cli/settings.json` | `SessionStart` `injectSteps` system message |
465
+ | Grok Build | topic notes under `~/.grok/memory-v2/`, imported to `/memories/grok/`; `[memory_v2] enabled = false` | `~/.grok/config.toml`; allow `MCPTool(context-graph__*)` | the MCP server's `instructions` (`mcp --session-context`): Grok's start hooks can't reach the model |
466
+
467
+ Antigravity's hooks carry no prompt or reply text, so its turns are read from
468
+ the transcript its `Stop` payload points at; that is what gives its sessions a
469
+ place in recall and in the next session's "where you left off".
470
+
471
+ `live-hooks-e2e.sh memory` checks this against each installed agent: a memory
472
+ written, one read back in a new session, and the next session's index and last
473
+ ask.
474
+
409
475
  ### Codex and Claude Code Plugins
410
476
 
411
477
  Both runtimes have a distributable Runtime Plugin that wires hooks and the MCP server; marketplace
@@ -5,12 +5,13 @@ Protocol, then routes those events to graph connectors. The installed command
5
5
  is the same for every runtime:
6
6
 
7
7
  ```bash
8
- memgraph-context-graph hook run <runtime> \
9
- --connector skills-graph \
10
- --connector actions-graph \
11
- --connector sessions-graph
8
+ memgraph-context-graph hook run <runtime>
12
9
  ```
13
10
 
11
+ It enables the graph connectors the config file's `[capture] connectors`
12
+ names (unset: `skills-graph`, `actions-graph`, `sessions-graph`). Repeated
13
+ `--connector` flags override that for one hook command.
14
+
14
15
  Runtime registrations are discovered through the
15
16
  `memgraph_context_graph.runtimes` Python entry-point group.
16
17
 
@@ -36,8 +37,9 @@ memgraph-context-graph hook init antigravity-cli
36
37
  memgraph-context-graph hook init grok
37
38
  ```
38
39
 
39
- `hook init` enables all three built-in connectors by default. Use repeated
40
- `--connector` flags to select a subset. Existing Copilot, Cursor, Antigravity, and Grok
40
+ `hook init` writes hook commands without `--connector` flags, so they enable
41
+ `[capture] connectors`. Repeated `--connector` flags pin a set into the
42
+ generated commands instead. Existing Copilot, Cursor, Antigravity, and Grok
41
43
  JSON documents are merged without removing unrelated settings or hooks.
42
44
  OpenCode's generated plugin is replaced only with `--force`.
43
45
 
@@ -139,15 +141,15 @@ never deletes its session, so those sessions only ever see turn ends.
139
141
  Antigravity CLI (`agy`, the successor to Gemini CLI) reads named hook groups
140
142
  from `.agents/hooks.json`; `hook init` owns the `memgraph-context-graph` group and
141
143
  merges it beside any others. Only the tool events (`PreToolUse`,
142
- `PostToolUse`) take matcher groups. `PreInvocation` and `Stop` are written as
144
+ `PostToolUse`) take matcher groups. `SessionStart` and `Stop` are written as
143
145
  flat handlers, because `agy` rejects the whole file when a lifecycle event is
144
146
  nested. Timeouts are in seconds.
145
147
 
146
148
  ```json
147
149
  {
148
150
  "memgraph-context-graph": {
149
- "PreInvocation": [
150
- {"type": "command", "command": "memgraph-context-graph hook run antigravity-cli --event-name PreInvocation", "timeout": 30}
151
+ "SessionStart": [
152
+ {"type": "command", "command": "memgraph-context-graph hook run antigravity-cli --event-name SessionStart", "timeout": 30}
151
153
  ],
152
154
  "PreToolUse": [
153
155
  {"hooks": [{"type": "command", "command": "memgraph-context-graph hook run antigravity-cli --event-name PreToolUse", "timeout": 30}]}
@@ -246,12 +248,11 @@ memgraph-context-graph setup antigravity-cli --project-dir "$PWD"
246
248
  Smoke-test a registration and its configured connectors:
247
249
 
248
250
  ```bash
249
- memgraph-context-graph doctor --runtime cursor \
250
- --connector skills-graph \
251
- --connector actions-graph \
252
- --connector sessions-graph
251
+ memgraph-context-graph doctor --runtime cursor
253
252
  ```
254
253
 
254
+ `doctor` checks the `[capture] connectors` set unless `--connector` flags name others.
255
+
255
256
  Each runtime provides its own native probe payload; `doctor` does not assume
256
257
  that every runtime calls its session-end event `Stop`.
257
258
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "memgraph-context-graph"
3
- version = "0.7.4"
3
+ version = "0.9.0"
4
4
  description = "Connect agent SDKs to context-graph components (actions-graph, skills-graph, etc.)"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -85,6 +85,11 @@ class HookConfig:
85
85
  memgraph_database: str = _MEMGRAPH_DEFAULTS["database"]
86
86
  openai_api_key: str = _LLM_DEFAULTS["openai_api_key"]
87
87
  anthropic_api_key: str = _LLM_DEFAULTS["anthropic_api_key"]
88
+ #: ``[llm] derive_model``: the model ontology derivation calls; None means the provider's default.
89
+ derive_model: str | None = None
90
+ #: ``[capture] connectors``: the graph connectors a hook enables when its command names none;
91
+ #: None (never set) means memgraph_context_graph.connectors.DEFAULT_CONNECTORS.
92
+ connectors: tuple[str, ...] | None = None
88
93
  auto_reconcile: bool | None = None
89
94
  #: The model recall embeds with; None means the consumer's own default.
90
95
  embedding_model: str | None = None
@@ -95,12 +100,18 @@ class HookConfig:
95
100
  ontology_path: str | None = None
96
101
  #: ``[ontology] derive``: "extend" (the default) or "off"; see sessions-graph's ontology module.
97
102
  ontology_derive: str | None = None
103
+ #: ``[extraction]`` keys (GLiNER2 checkpoint, thresholds, chunking), as written: sessions-graph
104
+ #: validates them, this module only keeps them across rewrites.
105
+ extraction_settings: dict[str, str] = field(default_factory=dict)
98
106
  #: ``[github] token``: overrides the ``gh`` login resources-graph's Sweep otherwise fetches with.
99
107
  github_token: str | None = None
100
108
  #: ``[memory] backend``: ``"context-graph"`` when the user has handed their
101
109
  #: harness memory to the graph (the ``memory`` tool and session-start index
102
110
  #: are on); ``"native"`` or None leaves the harness's own memory in charge.
103
111
  memory_backend: str | None = None
112
+ #: ``[session_start] resume``: whether a session starts with a brief of the
113
+ #: previous sessions in its project. None (never set) means on.
114
+ session_resume: bool | None = None
104
115
  #: The ``section.key`` names the file actually sets (top-level keys as ``key``), so a check can tell
105
116
  #: "left at the default" from "written": Enterprise refuses an explicit ``identity.user_id`` or
106
117
  #: ``memgraph.database``, which would quietly override what the login decides.
@@ -183,7 +194,7 @@ def resolve_auto_reconcile() -> bool:
183
194
  """Resolve whether sessions-graph should auto-trigger reconciliation on SESSION_END.
184
195
 
185
196
  Config file only (ADR 0002); never configured means ``False``, matching
186
- ``SessionsGraphConnector``'s default, since reconciliation costs LLM calls.
197
+ ``SessionsGraphConnector``'s default.
187
198
  """
188
199
  return bool(load_config().auto_reconcile)
189
200
 
@@ -193,6 +204,16 @@ def resolve_embedding_model() -> str | None:
193
204
  return load_config().embedding_model
194
205
 
195
206
 
207
+ def resolve_extraction() -> dict[str, str]:
208
+ """The configured ``[extraction]`` keys, as written; sessions-graph owns their meaning."""
209
+ return dict(load_config().extraction_settings)
210
+
211
+
212
+ def resolve_derive_model() -> str | None:
213
+ """The configured ``[llm] derive_model``, or None to leave the provider's default."""
214
+ return load_config().derive_model
215
+
216
+
196
217
  def resolve_ontology() -> tuple[str | None, str | None]:
197
218
  """The configured ``[ontology]`` (path, derive), each None when unset; sessions-graph owns their meaning."""
198
219
  config = load_config()
@@ -216,12 +237,15 @@ def write_config(
216
237
  memgraph_database: str | None = None,
217
238
  openai_api_key: str | None = None,
218
239
  anthropic_api_key: str | None = None,
240
+ derive_model: str | None = None,
241
+ connectors: tuple[str, ...] | None = None,
219
242
  auto_reconcile: bool | None = None,
220
243
  embedding_model: str | None = None,
221
244
  ontology_path: str | None = None,
222
245
  ontology_derive: str | None = None,
223
246
  github_token: str | None = None,
224
247
  memory_backend: str | None = None,
248
+ session_resume: bool | None = None,
225
249
  ) -> Path:
226
250
  """Write or update the config file. Returns the path written to.
227
251
 
@@ -237,12 +261,15 @@ def write_config(
237
261
  "memgraph_database": memgraph_database,
238
262
  "openai_api_key": openai_api_key,
239
263
  "anthropic_api_key": anthropic_api_key,
264
+ "derive_model": derive_model,
265
+ "connectors": connectors,
240
266
  "auto_reconcile": auto_reconcile,
241
267
  "embedding_model": embedding_model,
242
268
  "ontology_path": ontology_path,
243
269
  "ontology_derive": ontology_derive,
244
270
  "github_token": github_token,
245
271
  "memory_backend": memory_backend,
272
+ "session_resume": session_resume,
246
273
  }
247
274
  config = replace(_read_config_file(), **{key: value for key, value in updates.items() if value is not None})
248
275
  return _write_config_file(_render_config(config))
@@ -261,8 +288,8 @@ def write_full_config(
261
288
  ) -> Path:
262
289
  """Write a complete config file, resetting identity, Memgraph and LLM settings to what is given.
263
290
 
264
- Every other section (``[reconcile]``, ``[recall]``, ``[ontology]``, ``[github]``, ``[memory]``) and the
265
- edition are kept from the existing file, since only ``config set`` or a hand edit ever sets them.
291
+ Every other section (``[capture]``, ``[reconcile]``, ``[recall]``, ``[ontology]``, ``[extraction]``,
292
+ ``[github]``, ``[memory]``, ``[session_start]``), ``[llm] derive_model`` and the edition are kept from the existing file, since only ``config set`` or a hand edit ever sets them.
266
293
  ``auto_reconcile`` is kept likewise unless given here. :func:`write_config` is the partial update.
267
294
  """
268
295
  existing = _read_config_file()
@@ -308,11 +335,14 @@ def _read_config_file() -> HookConfig:
308
335
  identity = sections.get("identity", {})
309
336
  memgraph = sections.get("memgraph", {})
310
337
  llm = sections.get("llm", {})
338
+ capture = sections.get("capture", {})
311
339
  reconcile = sections.get("reconcile", {})
312
340
  recall = sections.get("recall", {})
313
341
  ontology = sections.get("ontology", {})
342
+ extraction = sections.get("extraction", {})
314
343
  github = sections.get("github", {})
315
344
  memory = sections.get("memory", {})
345
+ session_start = sections.get("session_start", {})
316
346
  auto_reconcile_raw = reconcile.get("auto_reconcile")
317
347
 
318
348
  return HookConfig(
@@ -324,13 +354,17 @@ def _read_config_file() -> HookConfig:
324
354
  memgraph_database=memgraph.get("database") or _MEMGRAPH_DEFAULTS["database"],
325
355
  openai_api_key=llm.get("openai_api_key", _LLM_DEFAULTS["openai_api_key"]),
326
356
  anthropic_api_key=llm.get("anthropic_api_key", _LLM_DEFAULTS["anthropic_api_key"]),
357
+ derive_model=llm.get("derive_model") or None,
358
+ connectors=_parse_list(capture["connectors"]) if "connectors" in capture else None,
327
359
  auto_reconcile=parse_bool_flag(auto_reconcile_raw) if auto_reconcile_raw is not None else None,
328
360
  embedding_model=recall.get("embedding_model") or None,
329
361
  recall_settings={key: value for key, value in recall.items() if key != "embedding_model"},
330
362
  ontology_path=ontology.get("path") or None,
331
363
  ontology_derive=ontology.get("derive") or None,
364
+ extraction_settings=dict(extraction),
332
365
  github_token=github.get("token") or None,
333
366
  memory_backend=memory.get("backend") or None,
367
+ session_resume=parse_bool_flag(session_start["resume"]) if session_start.get("resume") else None,
334
368
  present=frozenset(
335
369
  f"{section}.{key}" if section else key
336
370
  for section, keys in sections.items()
@@ -341,6 +375,11 @@ def _read_config_file() -> HookConfig:
341
375
  )
342
376
 
343
377
 
378
+ def _parse_list(value: str) -> tuple[str, ...]:
379
+ """A comma-separated value (``"a, b"``) as its non-blank items; the parser has no arrays."""
380
+ return tuple(item.strip() for item in value.split(",") if item.strip())
381
+
382
+
344
383
  def _parse_toml(path: Path, problems: list[str] | None = None) -> dict[str, dict[str, str]]:
345
384
  """Minimal TOML parser — handles [section] and key = "value" pairs.
346
385
 
@@ -379,8 +418,9 @@ def _render_config(config: HookConfig) -> str:
379
418
  """Render the full config file content.
380
419
 
381
420
  ``[reconcile]`` is omitted while ``auto_reconcile`` is ``None`` so a re-read keeps "never configured"
382
- distinct from an explicit ``false``; ``[recall]``, ``[ontology]``, ``[github]`` and ``[memory]`` are
383
- omitted while they hold nothing.
421
+ distinct from an explicit ``false``, and ``[session_start]`` likewise while ``session_resume`` is;
422
+ ``[capture]`` likewise while ``connectors`` is; ``[recall]``, ``[ontology]``, ``[extraction]``, ``[github]``
423
+ and ``[memory]`` are omitted while they hold nothing.
384
424
  """
385
425
  user_id = config.user_id or ""
386
426
  lines = [
@@ -400,7 +440,10 @@ def _render_config(config: HookConfig) -> str:
400
440
  "[llm]",
401
441
  f'openai_api_key = "{config.openai_api_key}"',
402
442
  f'anthropic_api_key = "{config.anthropic_api_key}"',
443
+ *([f'derive_model = "{config.derive_model}"'] if config.derive_model else []),
403
444
  ]
445
+ if config.connectors is not None:
446
+ lines += ["", "[capture]", f'connectors = "{", ".join(config.connectors)}"']
404
447
  if config.auto_reconcile is not None:
405
448
  lines += ["", "[reconcile]", f"auto_reconcile = {'true' if config.auto_reconcile else 'false'}"]
406
449
  recall = (
@@ -415,10 +458,14 @@ def _render_config(config: HookConfig) -> str:
415
458
  }
416
459
  if ontology:
417
460
  lines += ["", "[ontology]", *(f'{key} = "{value}"' for key, value in ontology.items())]
461
+ if config.extraction_settings:
462
+ lines += ["", "[extraction]", *(f'{key} = "{value}"' for key, value in config.extraction_settings.items())]
418
463
  if config.github_token:
419
464
  lines += ["", "[github]", f'token = "{config.github_token}"']
420
465
  if config.memory_backend:
421
466
  lines += ["", "[memory]", f'backend = "{config.memory_backend}"']
467
+ if config.session_resume is not None:
468
+ lines += ["", "[session_start]", f"resume = {'true' if config.session_resume else 'false'}"]
422
469
  lines.append("")
423
470
  return "\n".join(lines)
424
471
 
@@ -25,6 +25,24 @@ function capture(payload) {
25
25
  })
26
26
  }
27
27
 
28
+ // The hook's stdout, parsed: what the hook wants added to the session, or null.
29
+ function ask(payload) {
30
+ return new Promise((resolve) => {
31
+ const child = spawn(command[0], command.slice(1), { stdio: ["pipe", "pipe", "inherit"] })
32
+ let out = ""
33
+ child.stdout.on("data", (chunk) => (out += chunk))
34
+ child.on("error", () => resolve(null))
35
+ child.on("close", () => {
36
+ try {
37
+ resolve(out.trim() ? JSON.parse(out) : null)
38
+ } catch {
39
+ resolve(null)
40
+ }
41
+ })
42
+ child.stdin.end(JSON.stringify(payload))
43
+ })
44
+ }
45
+
28
46
  export default {
29
47
  id: "memgraph.memgraph-context-graph",
30
48
  async setup(ctx) {
@@ -43,6 +61,24 @@ export default {
43
61
  return capture({ hook_event_name: "session.created", session_id: sessionID, cwd, model })
44
62
  }
45
63
 
64
+ // The session-start text (memory index, where the user left off), asked for once per session. The context
65
+ // hook runs before every model request and rebuilds the system prompt each time, so it is re-added from
66
+ // this cache on each one; compaction and title generation build their own requests and get it too.
67
+ const sessionContext = new Map()
68
+ const addSessionContext = async (event) => {
69
+ if (!sessionContext.has(event.sessionID)) {
70
+ sessionContext.set(
71
+ event.sessionID,
72
+ ask({ hook_event_name: "session.context", session_id: event.sessionID, cwd: ctx.location?.directory }),
73
+ )
74
+ }
75
+ const response = await sessionContext.get(event.sessionID)
76
+ if (typeof response?.system === "string" && response.system) event.system.push({ type: "text", text: response.system })
77
+ }
78
+ for (const name of ["context", "compaction", "generate"]) {
79
+ registrations.push(await ctx.session.hook(name, addSessionContext))
80
+ }
81
+
46
82
  registrations.push(await ctx.session.hook("prompt", async (event) => {
47
83
  openTurns.add(event.sessionID)
48
84
  await start(event.sessionID, ctx.location?.directory)