memgraph-context-graph 0.7.4__tar.gz → 0.8.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 (81) hide show
  1. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/PKG-INFO +45 -8
  2. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/README.md +44 -7
  3. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/docs/command-hooks.md +3 -3
  4. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/pyproject.toml +1 -1
  5. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/adapters/_identity.py +13 -4
  6. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/adapters/_opencode_plugin.js +36 -0
  7. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/adapters/_spec.py +26 -1
  8. memgraph_context_graph-0.8.0/src/memgraph_context_graph/adapters/agent_mcp.py +326 -0
  9. memgraph_context_graph-0.8.0/src/memgraph_context_graph/adapters/agent_memory.py +201 -0
  10. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/adapters/antigravity_cli.py +77 -17
  11. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/adapters/claude_code.py +2 -0
  12. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/adapters/codex.py +2 -0
  13. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/adapters/copilot_cli.py +6 -1
  14. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/adapters/cursor.py +6 -1
  15. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/adapters/grok.py +2 -1
  16. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/adapters/opencode.py +5 -1
  17. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/cli.py +16 -4
  18. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/hooks/runner.py +26 -14
  19. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/mcp_server.py +20 -6
  20. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/setup/agents.py +58 -1
  21. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/setup/cli.py +1 -1
  22. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/setup/engine.py +25 -6
  23. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/setup/prompts.py +4 -8
  24. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/setup/tool.py +1 -1
  25. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/setup/uninstall.py +11 -5
  26. memgraph_context_graph-0.8.0/src/memgraph_context_graph/terminal.py +38 -0
  27. memgraph_context_graph-0.8.0/tests/test_agent_memory_wiring.py +215 -0
  28. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/tests/test_antigravity_cli_adapter.py +46 -7
  29. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/tests/test_setup.py +6 -4
  30. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/tests/test_setup_docker.py +3 -2
  31. memgraph_context_graph-0.8.0/tests/test_terminal.py +35 -0
  32. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/.gitignore +0 -0
  33. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/CONTEXT.md +0 -0
  34. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/LICENSE +0 -0
  35. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/docs/adr/0001-runtime-adapter-terminology.md +0 -0
  36. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/docs/adr/0002-config-file-only-hook-resolution.md +0 -0
  37. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/docs/adr/0003-config-path-override.md +0 -0
  38. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/docs/adr/0004-user-id-in-session-start-event.md +0 -0
  39. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/docs/adr/0005-config-file-covers-llm-key-and-subprocess-env.md +0 -0
  40. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/docs/adr/0006-memory-tool-writes-explicit-memory.md +0 -0
  41. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/docs/research/2026-10-codex-memory.md +0 -0
  42. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/__init__.py +0 -0
  43. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/adapters/__init__.py +0 -0
  44. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/adapters/claude.py +0 -0
  45. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/adapters/claude_code_memory.py +0 -0
  46. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/adapters/codex_memory.py +0 -0
  47. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/adapters/openai.py +0 -0
  48. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/capture_log.py +0 -0
  49. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/events.py +0 -0
  50. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/hooks/__init__.py +0 -0
  51. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/hooks/cli.py +0 -0
  52. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/hooks/runtime_plugin.py +0 -0
  53. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/link.py +0 -0
  54. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/memory_backend.py +0 -0
  55. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/protocols.py +0 -0
  56. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/setup/__init__.py +0 -0
  57. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/setup/memgraph.py +0 -0
  58. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/spaces.py +0 -0
  59. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/src/memgraph_context_graph/tools.py +0 -0
  60. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/tests/__init__.py +0 -0
  61. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/tests/conftest.py +0 -0
  62. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/tests/fake_agent_cli.py +0 -0
  63. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/tests/test_capture_log.py +0 -0
  64. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/tests/test_claude_code_adapter.py +0 -0
  65. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/tests/test_codex_adapter.py +0 -0
  66. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/tests/test_config_cli.py +0 -0
  67. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/tests/test_context_before_tool.py +0 -0
  68. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/tests/test_copilot_cli_adapter.py +0 -0
  69. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/tests/test_cursor_adapter.py +0 -0
  70. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/tests/test_dev_scripts.py +0 -0
  71. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/tests/test_events.py +0 -0
  72. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/tests/test_grok_adapter.py +0 -0
  73. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/tests/test_hook_cli.py +0 -0
  74. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/tests/test_identity.py +0 -0
  75. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/tests/test_link.py +0 -0
  76. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/tests/test_opencode_adapter.py +0 -0
  77. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/tests/test_plugin_hooks.py +0 -0
  78. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/tests/test_runner_sessions_graph_connector.py +0 -0
  79. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/tests/test_runtime_plugin.py +0 -0
  80. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/tests/test_spaces.py +0 -0
  81. {memgraph_context_graph-0.7.4 → memgraph_context_graph-0.8.0}/tests/test_tools.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.8.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
@@ -308,15 +308,23 @@ Hooks never read environment variables for these values, and neither does setup:
308
308
 
309
309
  The hooks write sessions into Memgraph; recall reads them back. With
310
310
  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:
311
+ tool over stdio MCP. The Codex and Claude Code plugins bundle that server, so
312
+ installing a plugin is all it takes; for the other agents, `setup` registers it
313
+ in each one's own user-level MCP config (see
314
+ [every agent](#every-agent-registration-switch-and-session-start)). The tool returns context, not an answer:
313
315
  the dated messages and facts from the user's own sessions that match the
314
316
  question, behind a header with the rules for reading them. The harness's model
315
317
  answers from those rows.
316
318
 
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.
319
+ At session start the hook adds a line to the model's context saying the tool
320
+ exists, then where the user left off in this project: the last session's date,
321
+ agent, first and last ask and last reply, and the first ask of the two before
322
+ it, quoted from captured turns. Sessions from any agent count, so a session in
323
+ Grok picks up where one in Codex stopped. It is a pointer, not a recall result:
324
+ nothing is ranked against a question, and the model still calls `recall` when
325
+ one needs memory. A session's project is its checkout's key (the same one
326
+ memory folders use), recorded when it starts. Turn it off with
327
+ `memgraph-context-graph config set session_start.resume false`.
320
328
 
321
329
  A connector may also add a line just before a tool runs
322
330
  (`GraphConnector.context_before_tool`) — resources-graph's Nudge that what the
@@ -359,8 +367,9 @@ was benchmarked in; the JSON is what `--json` prints.
359
367
 
360
368
  ### Memory: Context Graph as the harness's memory
361
369
 
362
- Claude Code and Codex each keep their own memory. Context Graph can replace it
363
- for a user who opts in:
370
+ Every agent `setup` knows can use Context Graph as its memory. Claude Code and
371
+ Codex each keep their own memory, and Context Graph replaces it for a user who
372
+ opts in:
364
373
 
365
374
  ```bash
366
375
  memgraph-context-graph setup claude-code --memory-backend context-graph
@@ -431,6 +440,34 @@ name is used. See
431
440
  [ADR 0006](docs/adr/0006-memory-tool-writes-explicit-memory.md) for why this is
432
441
  the one write the model makes over MCP.
433
442
 
443
+ #### Every agent: registration, switch and session start
444
+
445
+ The other five have no plugin, so `setup <runtime>` registers the
446
+ `context-graph` MCP server in the agent's own user-level config: memory belongs
447
+ to the user, not to one project. It adds the server's tools to the agent's
448
+ persistent allow-list where it keeps one, because a headless run refuses a tool
449
+ that needs approval. `--memory-backend context-graph` then turns the agent's
450
+ own memory off, and `uninstall` undoes both. Every edited file keeps the rest of
451
+ its content and is backed up once to `<name>.context-graph.bak`.
452
+
453
+ | Agent | Built-in memory, and the switch | Server registered in | Session-start text reaches the model through |
454
+ |---|---|---|---|
455
+ | Claude Code | auto memory; `autoMemoryEnabled: false` | the plugin | `SessionStart` `additionalContext` |
456
+ | Codex | memories; three keys in `config.toml` | the plugin | `SessionStart` `additionalContext` (~2,500-token cap) |
457
+ | 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` |
458
+ | 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) |
459
+ | 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 |
460
+ | 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 |
461
+ | 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 |
462
+
463
+ Antigravity's hooks carry no prompt or reply text, so its turns are read from
464
+ the transcript its `Stop` payload points at; that is what gives its sessions a
465
+ place in recall and in the next session's "where you left off".
466
+
467
+ `live-hooks-e2e.sh memory` checks this against each installed agent: a memory
468
+ written, one read back in a new session, and the next session's index and last
469
+ ask.
470
+
434
471
  ### Codex and Claude Code Plugins
435
472
 
436
473
  Both runtimes have a distributable Runtime Plugin that wires hooks and the MCP server; marketplace
@@ -283,15 +283,23 @@ Hooks never read environment variables for these values, and neither does setup:
283
283
 
284
284
  The hooks write sessions into Memgraph; recall reads them back. With
285
285
  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:
286
+ tool over stdio MCP. The Codex and Claude Code plugins bundle that server, so
287
+ installing a plugin is all it takes; for the other agents, `setup` registers it
288
+ in each one's own user-level MCP config (see
289
+ [every agent](#every-agent-registration-switch-and-session-start)). The tool returns context, not an answer:
288
290
  the dated messages and facts from the user's own sessions that match the
289
291
  question, behind a header with the rules for reading them. The harness's model
290
292
  answers from those rows.
291
293
 
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.
294
+ At session start the hook adds a line to the model's context saying the tool
295
+ exists, then where the user left off in this project: the last session's date,
296
+ agent, first and last ask and last reply, and the first ask of the two before
297
+ it, quoted from captured turns. Sessions from any agent count, so a session in
298
+ Grok picks up where one in Codex stopped. It is a pointer, not a recall result:
299
+ nothing is ranked against a question, and the model still calls `recall` when
300
+ one needs memory. A session's project is its checkout's key (the same one
301
+ memory folders use), recorded when it starts. Turn it off with
302
+ `memgraph-context-graph config set session_start.resume false`.
295
303
 
296
304
  A connector may also add a line just before a tool runs
297
305
  (`GraphConnector.context_before_tool`) — resources-graph's Nudge that what the
@@ -334,8 +342,9 @@ was benchmarked in; the JSON is what `--json` prints.
334
342
 
335
343
  ### Memory: Context Graph as the harness's memory
336
344
 
337
- Claude Code and Codex each keep their own memory. Context Graph can replace it
338
- for a user who opts in:
345
+ Every agent `setup` knows can use Context Graph as its memory. Claude Code and
346
+ Codex each keep their own memory, and Context Graph replaces it for a user who
347
+ opts in:
339
348
 
340
349
  ```bash
341
350
  memgraph-context-graph setup claude-code --memory-backend context-graph
@@ -406,6 +415,34 @@ name is used. See
406
415
  [ADR 0006](docs/adr/0006-memory-tool-writes-explicit-memory.md) for why this is
407
416
  the one write the model makes over MCP.
408
417
 
418
+ #### Every agent: registration, switch and session start
419
+
420
+ The other five have no plugin, so `setup <runtime>` registers the
421
+ `context-graph` MCP server in the agent's own user-level config: memory belongs
422
+ to the user, not to one project. It adds the server's tools to the agent's
423
+ persistent allow-list where it keeps one, because a headless run refuses a tool
424
+ that needs approval. `--memory-backend context-graph` then turns the agent's
425
+ own memory off, and `uninstall` undoes both. Every edited file keeps the rest of
426
+ its content and is backed up once to `<name>.context-graph.bak`.
427
+
428
+ | Agent | Built-in memory, and the switch | Server registered in | Session-start text reaches the model through |
429
+ |---|---|---|---|
430
+ | Claude Code | auto memory; `autoMemoryEnabled: false` | the plugin | `SessionStart` `additionalContext` |
431
+ | Codex | memories; three keys in `config.toml` | the plugin | `SessionStart` `additionalContext` (~2,500-token cap) |
432
+ | 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` |
433
+ | 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) |
434
+ | 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 |
435
+ | 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 |
436
+ | 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 |
437
+
438
+ Antigravity's hooks carry no prompt or reply text, so its turns are read from
439
+ the transcript its `Stop` payload points at; that is what gives its sessions a
440
+ place in recall and in the next session's "where you left off".
441
+
442
+ `live-hooks-e2e.sh memory` checks this against each installed agent: a memory
443
+ written, one read back in a new session, and the next session's index and last
444
+ ask.
445
+
409
446
  ### Codex and Claude Code Plugins
410
447
 
411
448
  Both runtimes have a distributable Runtime Plugin that wires hooks and the MCP server; marketplace
@@ -139,15 +139,15 @@ never deletes its session, so those sessions only ever see turn ends.
139
139
  Antigravity CLI (`agy`, the successor to Gemini CLI) reads named hook groups
140
140
  from `.agents/hooks.json`; `hook init` owns the `memgraph-context-graph` group and
141
141
  merges it beside any others. Only the tool events (`PreToolUse`,
142
- `PostToolUse`) take matcher groups. `PreInvocation` and `Stop` are written as
142
+ `PostToolUse`) take matcher groups. `SessionStart` and `Stop` are written as
143
143
  flat handlers, because `agy` rejects the whole file when a lifecycle event is
144
144
  nested. Timeouts are in seconds.
145
145
 
146
146
  ```json
147
147
  {
148
148
  "memgraph-context-graph": {
149
- "PreInvocation": [
150
- {"type": "command", "command": "memgraph-context-graph hook run antigravity-cli --event-name PreInvocation", "timeout": 30}
149
+ "SessionStart": [
150
+ {"type": "command", "command": "memgraph-context-graph hook run antigravity-cli --event-name SessionStart", "timeout": 30}
151
151
  ],
152
152
  "PreToolUse": [
153
153
  {"hooks": [{"type": "command", "command": "memgraph-context-graph hook run antigravity-cli --event-name PreToolUse", "timeout": 30}]}
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "memgraph-context-graph"
3
- version = "0.7.4"
3
+ version = "0.8.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" }
@@ -101,6 +101,9 @@ class HookConfig:
101
101
  #: harness memory to the graph (the ``memory`` tool and session-start index
102
102
  #: are on); ``"native"`` or None leaves the harness's own memory in charge.
103
103
  memory_backend: str | None = None
104
+ #: ``[session_start] resume``: whether a session starts with a brief of the
105
+ #: previous sessions in its project. None (never set) means on.
106
+ session_resume: bool | None = None
104
107
  #: The ``section.key`` names the file actually sets (top-level keys as ``key``), so a check can tell
105
108
  #: "left at the default" from "written": Enterprise refuses an explicit ``identity.user_id`` or
106
109
  #: ``memgraph.database``, which would quietly override what the login decides.
@@ -222,6 +225,7 @@ def write_config(
222
225
  ontology_derive: str | None = None,
223
226
  github_token: str | None = None,
224
227
  memory_backend: str | None = None,
228
+ session_resume: bool | None = None,
225
229
  ) -> Path:
226
230
  """Write or update the config file. Returns the path written to.
227
231
 
@@ -243,6 +247,7 @@ def write_config(
243
247
  "ontology_derive": ontology_derive,
244
248
  "github_token": github_token,
245
249
  "memory_backend": memory_backend,
250
+ "session_resume": session_resume,
246
251
  }
247
252
  config = replace(_read_config_file(), **{key: value for key, value in updates.items() if value is not None})
248
253
  return _write_config_file(_render_config(config))
@@ -261,8 +266,8 @@ def write_full_config(
261
266
  ) -> Path:
262
267
  """Write a complete config file, resetting identity, Memgraph and LLM settings to what is given.
263
268
 
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.
269
+ Every other section (``[reconcile]``, ``[recall]``, ``[ontology]``, ``[github]``, ``[memory]``,
270
+ ``[session_start]``) and the edition are kept from the existing file, since only ``config set`` or a hand edit ever sets them.
266
271
  ``auto_reconcile`` is kept likewise unless given here. :func:`write_config` is the partial update.
267
272
  """
268
273
  existing = _read_config_file()
@@ -313,6 +318,7 @@ def _read_config_file() -> HookConfig:
313
318
  ontology = sections.get("ontology", {})
314
319
  github = sections.get("github", {})
315
320
  memory = sections.get("memory", {})
321
+ session_start = sections.get("session_start", {})
316
322
  auto_reconcile_raw = reconcile.get("auto_reconcile")
317
323
 
318
324
  return HookConfig(
@@ -331,6 +337,7 @@ def _read_config_file() -> HookConfig:
331
337
  ontology_derive=ontology.get("derive") or None,
332
338
  github_token=github.get("token") or None,
333
339
  memory_backend=memory.get("backend") or None,
340
+ session_resume=parse_bool_flag(session_start["resume"]) if session_start.get("resume") else None,
334
341
  present=frozenset(
335
342
  f"{section}.{key}" if section else key
336
343
  for section, keys in sections.items()
@@ -379,8 +386,8 @@ def _render_config(config: HookConfig) -> str:
379
386
  """Render the full config file content.
380
387
 
381
388
  ``[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.
389
+ distinct from an explicit ``false``, and ``[session_start]`` likewise while ``session_resume`` is;
390
+ ``[recall]``, ``[ontology]``, ``[github]`` and ``[memory]`` are omitted while they hold nothing.
384
391
  """
385
392
  user_id = config.user_id or ""
386
393
  lines = [
@@ -419,6 +426,8 @@ def _render_config(config: HookConfig) -> str:
419
426
  lines += ["", "[github]", f'token = "{config.github_token}"']
420
427
  if config.memory_backend:
421
428
  lines += ["", "[memory]", f'backend = "{config.memory_backend}"']
429
+ if config.session_resume is not None:
430
+ lines += ["", "[session_start]", f"resume = {'true' if config.session_resume else 'false'}"]
422
431
  lines.append("")
423
432
  return "\n".join(lines)
424
433
 
@@ -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)
@@ -103,7 +103,10 @@ class RuntimeSpec:
103
103
  hook file, and is ignored. ``context_before_tool`` renders the stdout JSON
104
104
  that shows the model a line of context before a tool runs, leaving the call
105
105
  itself untouched; None where the runtime can't do that without denying or
106
- modifying the call.
106
+ modifying the call. ``session_context`` renders the stdout JSON that puts
107
+ the session-start text (memory index, where the user left off) in front of
108
+ the model when ``session_context_event`` fires; None where no hook output
109
+ reaches the model at session start.
107
110
  """
108
111
 
109
112
  name: str
@@ -119,6 +122,8 @@ class RuntimeSpec:
119
122
  foreign_payload_keys: frozenset[str] = frozenset()
120
123
  probe_payload: Mapping[str, Any] = field(default_factory=dict)
121
124
  context_before_tool: Callable[[str], Mapping[str, Any]] | None = None
125
+ session_context: Callable[[str], Mapping[str, Any]] | None = None
126
+ session_context_event: str = "SessionStart"
122
127
 
123
128
  def event_name(self, payload: Mapping[str, Any]) -> str:
124
129
  """Return *payload*'s canonical event name."""
@@ -202,6 +207,11 @@ class SpecAdapter(RuntimeAdapter):
202
207
  return ""
203
208
 
204
209
 
210
+ def hook_specific_session_context(text: str) -> dict[str, Any]:
211
+ """``SessionStart`` output that adds *text* to the model's context; Claude Code and Codex both read it."""
212
+ return {"hookSpecificOutput": {"hookEventName": "SessionStart", "additionalContext": text}}
213
+
214
+
205
215
  def pre_tool_use_context(text: str) -> dict[str, Any]:
206
216
  """``PreToolUse`` output that adds *text* to the model's context and leaves the call alone.
207
217
 
@@ -247,6 +257,21 @@ class SpecPlugin:
247
257
  render = self.spec.context_before_tool
248
258
  return None if render is None else dict(render(text))
249
259
 
260
+ def session_context_response(
261
+ self, payload: Mapping[str, Any], text: Callable[[], str | None]
262
+ ) -> dict[str, Any] | None:
263
+ """The stdout JSON that shows the model ``text()`` at session start; None for any other payload.
264
+
265
+ *text* is only called for this runtime's own session-start payload, since building it queries Memgraph.
266
+ """
267
+ render = self.spec.session_context
268
+ if render is None or self.spec.event_name(payload) != self.spec.session_context_event:
269
+ return None
270
+ if self.spec.foreign_payload_keys & payload.keys():
271
+ return None
272
+ content = text()
273
+ return dict(render(content)) if content else None
274
+
250
275
  def build_hooks_config(self, command: str, *, timeout: int = 30) -> dict[str, Any]:
251
276
  """Render the runtime's hook map using *command*."""
252
277
  return build_hooks_config(self.spec, command, timeout=timeout)