opencode-graphiti 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +176 -0
- package/esm/_dnt.shims.d.ts +6 -0
- package/esm/_dnt.shims.d.ts.map +1 -0
- package/esm/_dnt.shims.js +61 -0
- package/esm/mod.d.ts +2 -0
- package/esm/mod.d.ts.map +1 -0
- package/esm/mod.js +1 -0
- package/esm/package.json +3 -0
- package/esm/src/config.d.ts +3 -0
- package/esm/src/config.d.ts.map +1 -0
- package/esm/src/config.js +43 -0
- package/esm/src/index.d.ts +4 -0
- package/esm/src/index.d.ts.map +1 -0
- package/esm/src/index.js +436 -0
- package/esm/src/services/client.d.ts +37 -0
- package/esm/src/services/client.d.ts.map +1 -0
- package/esm/src/services/client.js +201 -0
- package/esm/src/services/compaction.d.ts +93 -0
- package/esm/src/services/compaction.d.ts.map +1 -0
- package/esm/src/services/compaction.js +144 -0
- package/esm/src/services/context.d.ts +3 -0
- package/esm/src/services/context.d.ts.map +1 -0
- package/esm/src/services/context.js +36 -0
- package/esm/src/services/logger.d.ts +7 -0
- package/esm/src/services/logger.d.ts.map +1 -0
- package/esm/src/services/logger.js +22 -0
- package/esm/src/types/index.d.ts +48 -0
- package/esm/src/types/index.d.ts.map +1 -0
- package/esm/src/types/index.js +1 -0
- package/package.json +34 -0
- package/script/_dnt.shims.d.ts +6 -0
- package/script/_dnt.shims.d.ts.map +1 -0
- package/script/_dnt.shims.js +65 -0
- package/script/mod.d.ts +2 -0
- package/script/mod.d.ts.map +1 -0
- package/script/mod.js +17 -0
- package/script/package.json +3 -0
- package/script/src/config.d.ts +3 -0
- package/script/src/config.d.ts.map +1 -0
- package/script/src/config.js +79 -0
- package/script/src/index.d.ts +4 -0
- package/script/src/index.d.ts.map +1 -0
- package/script/src/index.js +441 -0
- package/script/src/services/client.d.ts +37 -0
- package/script/src/services/client.d.ts.map +1 -0
- package/script/src/services/client.js +238 -0
- package/script/src/services/compaction.d.ts +93 -0
- package/script/src/services/compaction.d.ts.map +1 -0
- package/script/src/services/compaction.js +149 -0
- package/script/src/services/context.d.ts +3 -0
- package/script/src/services/context.d.ts.map +1 -0
- package/script/src/services/context.js +39 -0
- package/script/src/services/logger.d.ts +7 -0
- package/script/src/services/logger.d.ts.map +1 -0
- package/script/src/services/logger.js +61 -0
- package/script/src/types/index.d.ts +48 -0
- package/script/src/types/index.d.ts.map +1 -0
- package/script/src/types/index.js +2 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 vicary
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# opencode-graphiti
|
|
2
|
+
|
|
3
|
+
OpenCode plugin that provides persistent memory via a
|
|
4
|
+
[Graphiti](https://github.com/getzep/graphiti) knowledge graph.
|
|
5
|
+
|
|
6
|
+
## Motivation
|
|
7
|
+
|
|
8
|
+
Long-running AI coding sessions depend on persistent memory to stay on track.
|
|
9
|
+
Graphiti's MCP server is the intended backbone for this, but in practice it is
|
|
10
|
+
unreliable — connections drop, queries time out, and ingestion silently fails.
|
|
11
|
+
When the context window fills up and OpenCode triggers compaction, the
|
|
12
|
+
summarizer discards details that were never persisted. The result is **context
|
|
13
|
+
rot**: the agent loses track of recent decisions, re-explores solved problems,
|
|
14
|
+
and drifts away from the original goal.
|
|
15
|
+
|
|
16
|
+
This plugin exists to close that gap. It captures chat histories and project
|
|
17
|
+
facts into Graphiti when the server is healthy, then **re-injects them at the
|
|
18
|
+
start of every session and before every compaction** so the agent is always
|
|
19
|
+
reminded of recent project context — regardless of what survived the summary.
|
|
20
|
+
|
|
21
|
+
## Overview
|
|
22
|
+
|
|
23
|
+
This plugin connects to a Graphiti MCP server and:
|
|
24
|
+
|
|
25
|
+
- Injects relevant memories into the first user message of each session
|
|
26
|
+
- Detects user-triggered memory saves ("remember this", "keep in mind", etc.)
|
|
27
|
+
- Preserves key facts during context compaction
|
|
28
|
+
- Scopes memories per project using directory-based group IDs
|
|
29
|
+
|
|
30
|
+
## Prerequisites
|
|
31
|
+
|
|
32
|
+
A running
|
|
33
|
+
[Graphiti MCP server](https://github.com/getzep/graphiti/tree/main/mcp_server)
|
|
34
|
+
accessible over HTTP. The easiest way to set one up:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# Clone and start with Docker Compose
|
|
38
|
+
git clone https://github.com/getzep/graphiti.git
|
|
39
|
+
cd graphiti/mcp_server
|
|
40
|
+
docker compose up -d
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
This starts the MCP server at `http://localhost:8000/mcp` with a FalkorDB
|
|
44
|
+
backend.
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
### Option A: npm package (recommended)
|
|
49
|
+
|
|
50
|
+
Add the plugin to your `opencode.json` (or `opencode.jsonc`):
|
|
51
|
+
|
|
52
|
+
```jsonc
|
|
53
|
+
{
|
|
54
|
+
"plugin": ["opencode-graphiti"]
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Option B: Local build
|
|
59
|
+
|
|
60
|
+
Clone and build, then reference the built file:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
git clone https://github.com/vicary/opencode-graphiti.git
|
|
64
|
+
cd opencode-graphiti
|
|
65
|
+
deno task build
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Then add to your `opencode.json`:
|
|
69
|
+
|
|
70
|
+
```jsonc
|
|
71
|
+
{
|
|
72
|
+
"plugin": ["file:///absolute/path/to/opencode-graphiti/dist/index.js"]
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Option C: Plugin directory
|
|
77
|
+
|
|
78
|
+
Copy the built plugin into OpenCode's auto-loaded plugin directory:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# Global (all projects)
|
|
82
|
+
cp dist/index.js ~/.config/opencode/plugins/opencode-graphiti.js
|
|
83
|
+
|
|
84
|
+
# Or project-level
|
|
85
|
+
mkdir -p .opencode/plugins
|
|
86
|
+
cp dist/index.js .opencode/plugins/opencode-graphiti.js
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
No config entry needed — OpenCode loads plugins from these directories
|
|
90
|
+
automatically.
|
|
91
|
+
|
|
92
|
+
## Configuration
|
|
93
|
+
|
|
94
|
+
Create a config file at `~/.config/opencode/graphiti.jsonc`:
|
|
95
|
+
|
|
96
|
+
```jsonc
|
|
97
|
+
{
|
|
98
|
+
// Graphiti MCP server endpoint
|
|
99
|
+
"endpoint": "http://localhost:8000/mcp",
|
|
100
|
+
|
|
101
|
+
// Prefix for project group IDs (e.g. "opencode_my-project")
|
|
102
|
+
"groupIdPrefix": "opencode",
|
|
103
|
+
|
|
104
|
+
// Maximum results to retrieve
|
|
105
|
+
"maxFacts": 10,
|
|
106
|
+
"maxNodes": 5,
|
|
107
|
+
"maxEpisodes": 5,
|
|
108
|
+
|
|
109
|
+
// Feature toggles
|
|
110
|
+
"injectOnFirstMessage": true,
|
|
111
|
+
"enableTriggerDetection": true,
|
|
112
|
+
"enableCompactionSave": true
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
All fields are optional — defaults are used for any missing values.
|
|
117
|
+
|
|
118
|
+
## How It Works
|
|
119
|
+
|
|
120
|
+
### Memory Injection (`chat.message`)
|
|
121
|
+
|
|
122
|
+
On the first user message in a session, the plugin searches Graphiti for facts
|
|
123
|
+
and entities relevant to the message content. Matching results are formatted and
|
|
124
|
+
prepended to the conversation as a synthetic context block.
|
|
125
|
+
|
|
126
|
+
### Trigger Detection (`chat.message`)
|
|
127
|
+
|
|
128
|
+
User messages are scanned for phrases like:
|
|
129
|
+
|
|
130
|
+
- "remember this", "memorize that"
|
|
131
|
+
- "save this in memory", "keep this in mind"
|
|
132
|
+
- "don't forget", "note this"
|
|
133
|
+
- "for future reference"
|
|
134
|
+
|
|
135
|
+
When detected, the message content is saved as an episode in Graphiti.
|
|
136
|
+
|
|
137
|
+
### Compaction Preservation (`event` + `experimental.session.compacting`)
|
|
138
|
+
|
|
139
|
+
When OpenCode compacts the context window:
|
|
140
|
+
|
|
141
|
+
1. **Before compaction**: The plugin injects known facts into the compaction
|
|
142
|
+
context, so the summarizer preserves important knowledge.
|
|
143
|
+
2. **After compaction**: If a summary is produced, it is saved as an episode to
|
|
144
|
+
Graphiti, ensuring knowledge survives across compaction boundaries.
|
|
145
|
+
|
|
146
|
+
### Project Scoping
|
|
147
|
+
|
|
148
|
+
Each project gets a unique `group_id` derived from its directory name (e.g.
|
|
149
|
+
`opencode_my-project`). Group IDs only allow letters, numbers, dashes, and
|
|
150
|
+
underscores (colons are not allowed). This ensures memories from different
|
|
151
|
+
projects stay isolated.
|
|
152
|
+
|
|
153
|
+
## Development
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
# Format
|
|
157
|
+
deno fmt
|
|
158
|
+
|
|
159
|
+
# Lint
|
|
160
|
+
deno lint
|
|
161
|
+
|
|
162
|
+
# Type check
|
|
163
|
+
deno check src/index.ts
|
|
164
|
+
|
|
165
|
+
# Build
|
|
166
|
+
deno task build
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## License
|
|
170
|
+
|
|
171
|
+
MIT
|
|
172
|
+
|
|
173
|
+
## Acknowledgement
|
|
174
|
+
|
|
175
|
+
This project is inspired by
|
|
176
|
+
[opencode-openmemory](https://github.com/happycastle114/opencode-openmemory)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_dnt.shims.d.ts","sourceRoot":"","sources":["../src/_dnt.shims.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAKvC,eAAO,MAAM,aAAa;;CAA2C,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Deno } from "@deno/shim-deno";
|
|
2
|
+
export { Deno } from "@deno/shim-deno";
|
|
3
|
+
const dntGlobals = {
|
|
4
|
+
Deno,
|
|
5
|
+
};
|
|
6
|
+
export const dntGlobalThis = createMergeProxy(globalThis, dntGlobals);
|
|
7
|
+
function createMergeProxy(baseObj, extObj) {
|
|
8
|
+
return new Proxy(baseObj, {
|
|
9
|
+
get(_target, prop, _receiver) {
|
|
10
|
+
if (prop in extObj) {
|
|
11
|
+
return extObj[prop];
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
return baseObj[prop];
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
set(_target, prop, value) {
|
|
18
|
+
if (prop in extObj) {
|
|
19
|
+
delete extObj[prop];
|
|
20
|
+
}
|
|
21
|
+
baseObj[prop] = value;
|
|
22
|
+
return true;
|
|
23
|
+
},
|
|
24
|
+
deleteProperty(_target, prop) {
|
|
25
|
+
let success = false;
|
|
26
|
+
if (prop in extObj) {
|
|
27
|
+
delete extObj[prop];
|
|
28
|
+
success = true;
|
|
29
|
+
}
|
|
30
|
+
if (prop in baseObj) {
|
|
31
|
+
delete baseObj[prop];
|
|
32
|
+
success = true;
|
|
33
|
+
}
|
|
34
|
+
return success;
|
|
35
|
+
},
|
|
36
|
+
ownKeys(_target) {
|
|
37
|
+
const baseKeys = Reflect.ownKeys(baseObj);
|
|
38
|
+
const extKeys = Reflect.ownKeys(extObj);
|
|
39
|
+
const extKeysSet = new Set(extKeys);
|
|
40
|
+
return [...baseKeys.filter((k) => !extKeysSet.has(k)), ...extKeys];
|
|
41
|
+
},
|
|
42
|
+
defineProperty(_target, prop, desc) {
|
|
43
|
+
if (prop in extObj) {
|
|
44
|
+
delete extObj[prop];
|
|
45
|
+
}
|
|
46
|
+
Reflect.defineProperty(baseObj, prop, desc);
|
|
47
|
+
return true;
|
|
48
|
+
},
|
|
49
|
+
getOwnPropertyDescriptor(_target, prop) {
|
|
50
|
+
if (prop in extObj) {
|
|
51
|
+
return Reflect.getOwnPropertyDescriptor(extObj, prop);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
return Reflect.getOwnPropertyDescriptor(baseObj, prop);
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
has(_target, prop) {
|
|
58
|
+
return prop in extObj || prop in baseObj;
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
package/esm/mod.d.ts
ADDED
package/esm/mod.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../src/mod.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC"}
|
package/esm/mod.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./src/index.js";
|
package/esm/package.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/src/config.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AA0BvD,wBAAgB,UAAU,IAAI,cAAc,CAe3C"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as dntShim from "../_dnt.shims.js";
|
|
2
|
+
import { cosmiconfigSync } from "cosmiconfig";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
const DEFAULT_CONFIG = {
|
|
7
|
+
endpoint: "http://localhost:8000/mcp",
|
|
8
|
+
groupIdPrefix: "opencode",
|
|
9
|
+
maxFacts: 10,
|
|
10
|
+
maxNodes: 5,
|
|
11
|
+
maxEpisodes: 5,
|
|
12
|
+
injectOnFirstMessage: true,
|
|
13
|
+
enableCompactionSave: true,
|
|
14
|
+
compactionThreshold: 0.8,
|
|
15
|
+
minTokensForCompaction: 50000,
|
|
16
|
+
compactionCooldownMs: 30000,
|
|
17
|
+
autoResumeAfterCompaction: true,
|
|
18
|
+
};
|
|
19
|
+
function parseJsonc(text) {
|
|
20
|
+
let stripped = text.replace(/(^|\s)\/\/.*$/gm, "$1");
|
|
21
|
+
stripped = stripped.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
22
|
+
return JSON.parse(stripped);
|
|
23
|
+
}
|
|
24
|
+
const createExplorer = (cwd) => {
|
|
25
|
+
return cosmiconfigSync("graphiti", { stopDir: cwd });
|
|
26
|
+
};
|
|
27
|
+
export function loadConfig() {
|
|
28
|
+
const cwd = dntShim.Deno.cwd();
|
|
29
|
+
const explorer = createExplorer(cwd);
|
|
30
|
+
const result = explorer.search(cwd);
|
|
31
|
+
let config = result?.config;
|
|
32
|
+
if (!config) {
|
|
33
|
+
const configPath = join(homedir(), ".config", "opencode", "graphiti.jsonc");
|
|
34
|
+
try {
|
|
35
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
36
|
+
config = parseJsonc(raw);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
config = null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return { ...DEFAULT_CONFIG, ...(config ?? {}) };
|
|
43
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAC;AAqB/D,eAAO,MAAM,WAAW,GAAI,QAAQ,MAAM,EAAE,YAAY,MAAM,KAAG,MAKhE,CAAC;AAeF,eAAO,MAAM,QAAQ,EAAE,MA+ftB,CAAC"}
|