opencode-graphiti 0.0.0-development
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 +358 -0
- package/esm/_dnt.polyfills.d.ts +166 -0
- package/esm/_dnt.polyfills.d.ts.map +1 -0
- package/esm/_dnt.polyfills.js +177 -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/deno.d.ts +45 -0
- package/esm/deno.d.ts.map +1 -0
- package/esm/deno.js +39 -0
- package/esm/mod.d.ts +3 -0
- package/esm/mod.d.ts.map +1 -0
- package/esm/mod.js +2 -0
- package/esm/package.json +3 -0
- package/esm/src/config.d.ts +20 -0
- package/esm/src/config.d.ts.map +1 -0
- package/esm/src/config.js +246 -0
- package/esm/src/handlers/chat.d.ts +14 -0
- package/esm/src/handlers/chat.d.ts.map +1 -0
- package/esm/src/handlers/chat.js +60 -0
- package/esm/src/handlers/compacting.d.ts +9 -0
- package/esm/src/handlers/compacting.d.ts.map +1 -0
- package/esm/src/handlers/compacting.js +30 -0
- package/esm/src/handlers/event.d.ts +22 -0
- package/esm/src/handlers/event.d.ts.map +1 -0
- package/esm/src/handlers/event.js +287 -0
- package/esm/src/handlers/messages.d.ts +9 -0
- package/esm/src/handlers/messages.d.ts.map +1 -0
- package/esm/src/handlers/messages.js +93 -0
- package/esm/src/index.d.ts +5 -0
- package/esm/src/index.d.ts.map +1 -0
- package/esm/src/index.js +153 -0
- package/esm/src/services/batch-drain.d.ts +23 -0
- package/esm/src/services/batch-drain.d.ts.map +1 -0
- package/esm/src/services/batch-drain.js +217 -0
- package/esm/src/services/connection-manager.d.ts +104 -0
- package/esm/src/services/connection-manager.d.ts.map +1 -0
- package/esm/src/services/connection-manager.js +621 -0
- package/esm/src/services/constants.d.ts +7 -0
- package/esm/src/services/constants.d.ts.map +1 -0
- package/esm/src/services/constants.js +6 -0
- package/esm/src/services/context-limit.d.ts +3 -0
- package/esm/src/services/context-limit.d.ts.map +1 -0
- package/esm/src/services/context-limit.js +44 -0
- package/esm/src/services/event-extractor.d.ts +29 -0
- package/esm/src/services/event-extractor.d.ts.map +1 -0
- package/esm/src/services/event-extractor.js +659 -0
- package/esm/src/services/graphiti-async.d.ts +22 -0
- package/esm/src/services/graphiti-async.d.ts.map +1 -0
- package/esm/src/services/graphiti-async.js +219 -0
- package/esm/src/services/graphiti-mcp.d.ts +57 -0
- package/esm/src/services/graphiti-mcp.d.ts.map +1 -0
- package/esm/src/services/graphiti-mcp.js +194 -0
- package/esm/src/services/logger.d.ts +9 -0
- package/esm/src/services/logger.d.ts.map +1 -0
- package/esm/src/services/logger.js +104 -0
- package/esm/src/services/opencode-warning.d.ts +8 -0
- package/esm/src/services/opencode-warning.d.ts.map +1 -0
- package/esm/src/services/opencode-warning.js +104 -0
- package/esm/src/services/redis-cache.d.ts +27 -0
- package/esm/src/services/redis-cache.d.ts.map +1 -0
- package/esm/src/services/redis-cache.js +215 -0
- package/esm/src/services/redis-client.d.ts +89 -0
- package/esm/src/services/redis-client.d.ts.map +1 -0
- package/esm/src/services/redis-client.js +906 -0
- package/esm/src/services/redis-events.d.ts +46 -0
- package/esm/src/services/redis-events.d.ts.map +1 -0
- package/esm/src/services/redis-events.js +517 -0
- package/esm/src/services/redis-snapshot.d.ts +16 -0
- package/esm/src/services/redis-snapshot.d.ts.map +1 -0
- package/esm/src/services/redis-snapshot.js +184 -0
- package/esm/src/services/render-utils.d.ts +23 -0
- package/esm/src/services/render-utils.d.ts.map +1 -0
- package/esm/src/services/render-utils.js +149 -0
- package/esm/src/services/runtime-teardown.d.ts +23 -0
- package/esm/src/services/runtime-teardown.d.ts.map +1 -0
- package/esm/src/services/runtime-teardown.js +119 -0
- package/esm/src/services/sdk-normalize.d.ts +55 -0
- package/esm/src/services/sdk-normalize.d.ts.map +1 -0
- package/esm/src/services/sdk-normalize.js +61 -0
- package/esm/src/session.d.ts +74 -0
- package/esm/src/session.d.ts.map +1 -0
- package/esm/src/session.js +694 -0
- package/esm/src/types/index.d.ts +120 -0
- package/esm/src/types/index.d.ts.map +1 -0
- package/esm/src/types/index.js +28 -0
- package/esm/src/utils.d.ts +27 -0
- package/esm/src/utils.d.ts.map +1 -0
- package/esm/src/utils.js +76 -0
- package/package.json +59 -0
- package/script/_dnt.polyfills.d.ts +166 -0
- package/script/_dnt.polyfills.d.ts.map +1 -0
- package/script/_dnt.polyfills.js +180 -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/deno.d.ts +45 -0
- package/script/deno.d.ts.map +1 -0
- package/script/deno.js +41 -0
- package/script/mod.d.ts +3 -0
- package/script/mod.d.ts.map +1 -0
- package/script/mod.js +6 -0
- package/script/package.json +3 -0
- package/script/src/config.d.ts +20 -0
- package/script/src/config.d.ts.map +1 -0
- package/script/src/config.js +256 -0
- package/script/src/handlers/chat.d.ts +14 -0
- package/script/src/handlers/chat.d.ts.map +1 -0
- package/script/src/handlers/chat.js +63 -0
- package/script/src/handlers/compacting.d.ts +9 -0
- package/script/src/handlers/compacting.d.ts.map +1 -0
- package/script/src/handlers/compacting.js +33 -0
- package/script/src/handlers/event.d.ts +22 -0
- package/script/src/handlers/event.d.ts.map +1 -0
- package/script/src/handlers/event.js +290 -0
- package/script/src/handlers/messages.d.ts +9 -0
- package/script/src/handlers/messages.d.ts.map +1 -0
- package/script/src/handlers/messages.js +96 -0
- package/script/src/index.d.ts +5 -0
- package/script/src/index.d.ts.map +1 -0
- package/script/src/index.js +159 -0
- package/script/src/services/batch-drain.d.ts +23 -0
- package/script/src/services/batch-drain.d.ts.map +1 -0
- package/script/src/services/batch-drain.js +221 -0
- package/script/src/services/connection-manager.d.ts +104 -0
- package/script/src/services/connection-manager.d.ts.map +1 -0
- package/script/src/services/connection-manager.js +635 -0
- package/script/src/services/constants.d.ts +7 -0
- package/script/src/services/constants.d.ts.map +1 -0
- package/script/src/services/constants.js +9 -0
- package/script/src/services/context-limit.d.ts +3 -0
- package/script/src/services/context-limit.d.ts.map +1 -0
- package/script/src/services/context-limit.js +47 -0
- package/script/src/services/event-extractor.d.ts +29 -0
- package/script/src/services/event-extractor.d.ts.map +1 -0
- package/script/src/services/event-extractor.js +669 -0
- package/script/src/services/graphiti-async.d.ts +22 -0
- package/script/src/services/graphiti-async.d.ts.map +1 -0
- package/script/src/services/graphiti-async.js +223 -0
- package/script/src/services/graphiti-mcp.d.ts +57 -0
- package/script/src/services/graphiti-mcp.d.ts.map +1 -0
- package/script/src/services/graphiti-mcp.js +198 -0
- package/script/src/services/logger.d.ts +9 -0
- package/script/src/services/logger.d.ts.map +1 -0
- package/script/src/services/logger.js +142 -0
- package/script/src/services/opencode-warning.d.ts +8 -0
- package/script/src/services/opencode-warning.d.ts.map +1 -0
- package/script/src/services/opencode-warning.js +114 -0
- package/script/src/services/redis-cache.d.ts +27 -0
- package/script/src/services/redis-cache.d.ts.map +1 -0
- package/script/src/services/redis-cache.js +219 -0
- package/script/src/services/redis-client.d.ts +89 -0
- package/script/src/services/redis-client.d.ts.map +1 -0
- package/script/src/services/redis-client.js +943 -0
- package/script/src/services/redis-events.d.ts +46 -0
- package/script/src/services/redis-events.d.ts.map +1 -0
- package/script/src/services/redis-events.js +535 -0
- package/script/src/services/redis-snapshot.d.ts +16 -0
- package/script/src/services/redis-snapshot.d.ts.map +1 -0
- package/script/src/services/redis-snapshot.js +189 -0
- package/script/src/services/render-utils.d.ts +23 -0
- package/script/src/services/render-utils.d.ts.map +1 -0
- package/script/src/services/render-utils.js +165 -0
- package/script/src/services/runtime-teardown.d.ts +23 -0
- package/script/src/services/runtime-teardown.d.ts.map +1 -0
- package/script/src/services/runtime-teardown.js +155 -0
- package/script/src/services/sdk-normalize.d.ts +55 -0
- package/script/src/services/sdk-normalize.d.ts.map +1 -0
- package/script/src/services/sdk-normalize.js +67 -0
- package/script/src/session.d.ts +74 -0
- package/script/src/session.d.ts.map +1 -0
- package/script/src/session.js +698 -0
- package/script/src/types/index.d.ts +120 -0
- package/script/src/types/index.d.ts.map +1 -0
- package/script/src/types/index.js +33 -0
- package/script/src/utils.d.ts +27 -0
- package/script/src/utils.d.ts.map +1 -0
- package/script/src/utils.js +87 -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,358 @@
|
|
|
1
|
+
# opencode-graphiti
|
|
2
|
+
|
|
3
|
+
OpenCode plugin that gives your AI agent **short-term memory** and **long-term
|
|
4
|
+
memory**.
|
|
5
|
+
|
|
6
|
+
**Short-term memory** continuously summarizes and compacts every meaningful
|
|
7
|
+
session event — decisions, active tasks, file edits, errors, and more — into a
|
|
8
|
+
priority-tiered snapshot that is re-injected before every LLM call and every
|
|
9
|
+
compaction. The result is a rolling window of session continuity that
|
|
10
|
+
effectively extends the usable context far beyond the model's native limit: the
|
|
11
|
+
agent always knows what it was doing, even after the conversation is compacted.
|
|
12
|
+
|
|
13
|
+
**Long-term memory** persists knowledge across sessions via a
|
|
14
|
+
[Graphiti](https://github.com/getzep/graphiti) knowledge graph, so the agent can
|
|
15
|
+
recall project facts, past decisions, and learned preferences from earlier work
|
|
16
|
+
— not just the current session.
|
|
17
|
+
|
|
18
|
+
## Motivation
|
|
19
|
+
|
|
20
|
+
Long-running AI coding sessions depend on persistent memory to stay on track.
|
|
21
|
+
When the context window fills up and OpenCode triggers compaction, the
|
|
22
|
+
summarizer discards details that were never captured outside the conversation.
|
|
23
|
+
The result is **context rot**: the agent loses track of recent decisions,
|
|
24
|
+
re-explores solved problems, and drifts away from the original goal.
|
|
25
|
+
|
|
26
|
+
Graphiti's MCP server is a powerful knowledge-graph backend, but calling it on
|
|
27
|
+
every message adds latency and introduces a single point of failure —
|
|
28
|
+
connections drop, queries time out, and ingestion silently fails.
|
|
29
|
+
|
|
30
|
+
This plugin exists to close both gaps.
|
|
31
|
+
|
|
32
|
+
**Short-term memory** captures every meaningful event during the session —
|
|
33
|
+
decisions, task progress, file edits, errors, environment changes — and
|
|
34
|
+
continuously summarizes them into a compact, priority-tiered snapshot. That
|
|
35
|
+
snapshot is re-injected before every LLM call and before every compaction, so
|
|
36
|
+
the agent always retains a coherent picture of the active workstream. Because
|
|
37
|
+
the snapshot is continuously rebuilt from structured events rather than raw
|
|
38
|
+
conversation text, it survives compaction intact: the model picks up exactly
|
|
39
|
+
where it left off, no matter how many times the conversation has been
|
|
40
|
+
summarized. In practice, this creates a rolling session memory that extends the
|
|
41
|
+
effective context window well beyond the model's native limit.
|
|
42
|
+
|
|
43
|
+
**Long-term memory** lives in Graphiti's knowledge graph, which is updated in
|
|
44
|
+
the background so it never slows down your conversation. It provides
|
|
45
|
+
cross-session recall — project facts, past decisions, and learned preferences
|
|
46
|
+
from earlier sessions — cached locally for instant injection alongside the
|
|
47
|
+
short-term snapshot.
|
|
48
|
+
|
|
49
|
+
## Overview
|
|
50
|
+
|
|
51
|
+
This plugin uses a two-layer memory architecture:
|
|
52
|
+
|
|
53
|
+
**Short-term memory — continuously summarized session continuity:**
|
|
54
|
+
|
|
55
|
+
- Captures every meaningful event (decisions, tasks, file edits, errors,
|
|
56
|
+
environment changes) as structured session events
|
|
57
|
+
- Continuously rebuilds a priority-tiered snapshot from those events, keeping
|
|
58
|
+
the most important context within a tight budget
|
|
59
|
+
- Re-injects the snapshot before every LLM call and every compaction as a
|
|
60
|
+
`<session_memory>` block, so the agent never loses track of the active
|
|
61
|
+
workstream — even after repeated compactions
|
|
62
|
+
- Detects topic drift and schedules a background refresh of cached long-term
|
|
63
|
+
facts when the conversation shifts
|
|
64
|
+
|
|
65
|
+
**Long-term memory — persistent cross-session recall via Graphiti:**
|
|
66
|
+
|
|
67
|
+
- Sends buffered session events to Graphiti as episodes on idle or before
|
|
68
|
+
compaction
|
|
69
|
+
- Refreshes the local memory cache from Graphiti search results in the
|
|
70
|
+
background
|
|
71
|
+
- Provides cross-session recall via vector/graph search, cached locally for
|
|
72
|
+
instant injection alongside the short-term snapshot
|
|
73
|
+
- Saves compaction summaries as episodes so knowledge survives across session
|
|
74
|
+
boundaries
|
|
75
|
+
|
|
76
|
+
Graphiti stays off the steady-state hook path entirely: hook-time injection uses
|
|
77
|
+
only Redis/local cached recall, while fresh Graphiti data arrives through the
|
|
78
|
+
existing background refresh path on later turns.
|
|
79
|
+
|
|
80
|
+
## Prerequisites
|
|
81
|
+
|
|
82
|
+
Start the
|
|
83
|
+
[Graphiti MCP server](https://github.com/getzep/graphiti/tree/main/mcp_server)
|
|
84
|
+
with its default [FalkorDB](https://www.falkordb.com/) backend:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
git clone https://github.com/getzep/graphiti.git
|
|
88
|
+
cd graphiti/mcp_server
|
|
89
|
+
docker compose up -d
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
This starts Graphiti at `http://localhost:8000/mcp` and FalkorDB/Redis on
|
|
93
|
+
`localhost:6379`.
|
|
94
|
+
|
|
95
|
+
This plugin reuses that same FalkorDB/Redis storage layer alongside Graphiti: it
|
|
96
|
+
keeps short-term memory locally for every turn, while Graphiti builds the
|
|
97
|
+
long-term knowledge graph on top of the same backend.
|
|
98
|
+
|
|
99
|
+
> **Note:** Graphiti is optional for basic operation. If Graphiti is
|
|
100
|
+
> unavailable, the plugin continues to function with FalkorDB/Redis-sourced
|
|
101
|
+
> session memory; only the `<persistent_memory>` section (long-term
|
|
102
|
+
> cross-session facts) will be empty until Graphiti comes online.
|
|
103
|
+
|
|
104
|
+
## Installation
|
|
105
|
+
|
|
106
|
+
### Option A: npm package (recommended)
|
|
107
|
+
|
|
108
|
+
Add the plugin to your `opencode.json` (or `opencode.jsonc`):
|
|
109
|
+
|
|
110
|
+
```jsonc
|
|
111
|
+
{
|
|
112
|
+
"plugin": ["opencode-graphiti"]
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Option B: Local build
|
|
117
|
+
|
|
118
|
+
Local distributable builds are not a routine local setup step: `deno task
|
|
119
|
+
build`
|
|
120
|
+
requires an explicit `VERSION` via `dnt.ts`. If you already have a built
|
|
121
|
+
artifact, add it to your `opencode.json`:
|
|
122
|
+
|
|
123
|
+
```jsonc
|
|
124
|
+
{
|
|
125
|
+
"plugin": ["file:///absolute/path/to/opencode-graphiti/dist/esm/mod.js"]
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Option C: Plugin directory
|
|
130
|
+
|
|
131
|
+
Copy the built plugin into OpenCode's auto-loaded plugin directory:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
# Global (all projects)
|
|
135
|
+
cp dist/esm/mod.js ~/.config/opencode/plugins/opencode-graphiti.js
|
|
136
|
+
|
|
137
|
+
# Or project-level
|
|
138
|
+
mkdir -p .opencode/plugins
|
|
139
|
+
cp dist/esm/mod.js .opencode/plugins/opencode-graphiti.js
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
No config entry needed — OpenCode loads plugins from these directories
|
|
143
|
+
automatically.
|
|
144
|
+
|
|
145
|
+
## Configuration
|
|
146
|
+
|
|
147
|
+
Supported config locations, in lookup order:
|
|
148
|
+
|
|
149
|
+
1. The provided project directory: `package.json#graphiti`, `.graphitirc`, and
|
|
150
|
+
other standard `cosmiconfig` `graphiti` filenames
|
|
151
|
+
2. Standard global/home `graphiti` config locations discovered by `cosmiconfig`
|
|
152
|
+
(for example `~/.graphitirc`)
|
|
153
|
+
3. Legacy fallback: `~/.config/opencode/.graphitirc`
|
|
154
|
+
|
|
155
|
+
### Nested Config Shape (recommended)
|
|
156
|
+
|
|
157
|
+
```jsonc
|
|
158
|
+
{
|
|
159
|
+
"redis": {
|
|
160
|
+
// Redis endpoint used for the plugin hot tier
|
|
161
|
+
"endpoint": "redis://localhost:6379",
|
|
162
|
+
// Max events per drain batch
|
|
163
|
+
"batchSize": 20,
|
|
164
|
+
// Max combined body bytes per drain batch
|
|
165
|
+
"batchMaxBytes": 51200,
|
|
166
|
+
// Session event TTL in seconds (default: 24 h)
|
|
167
|
+
"sessionTtlSeconds": 86400,
|
|
168
|
+
// Memory cache TTL in seconds (default: 10 min)
|
|
169
|
+
"cacheTtlSeconds": 600,
|
|
170
|
+
// Max drain retry attempts before dead-lettering
|
|
171
|
+
"drainRetryMax": 3
|
|
172
|
+
},
|
|
173
|
+
"graphiti": {
|
|
174
|
+
// Graphiti MCP server endpoint
|
|
175
|
+
"endpoint": "http://localhost:8000/mcp",
|
|
176
|
+
// Prefix for project group IDs (e.g. "opencode-my-project")
|
|
177
|
+
"groupIdPrefix": "opencode",
|
|
178
|
+
// Jaccard similarity threshold (0–1) below which cache is refreshed
|
|
179
|
+
"driftThreshold": 0.5
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
All fields are optional — defaults (shown above) are used for any missing
|
|
185
|
+
values. Canonical nested values take precedence when both forms are supplied.
|
|
186
|
+
|
|
187
|
+
### Retained Compatibility
|
|
188
|
+
|
|
189
|
+
The canonical hot-tier config shape is now `redis.*`. For backward
|
|
190
|
+
compatibility, the loader still accepts legacy nested `falkordb.*` values. Only
|
|
191
|
+
the original Graphiti top-level aliases remain supported. Precedence is:
|
|
192
|
+
|
|
193
|
+
1. `redis.*` (canonical)
|
|
194
|
+
2. `falkordb.*` (legacy nested compatibility)
|
|
195
|
+
3. top-level Graphiti aliases such as `endpoint` and `groupIdPrefix`
|
|
196
|
+
|
|
197
|
+
### Legacy Top-Level Keys
|
|
198
|
+
|
|
199
|
+
For backward compatibility, the following original Graphiti top-level keys are
|
|
200
|
+
still accepted and map to their nested equivalents:
|
|
201
|
+
|
|
202
|
+
| Legacy key | Nested equivalent |
|
|
203
|
+
| ---------------- | ------------------------- |
|
|
204
|
+
| `endpoint` | `graphiti.endpoint` |
|
|
205
|
+
| `groupIdPrefix` | `graphiti.groupIdPrefix` |
|
|
206
|
+
| `driftThreshold` | `graphiti.driftThreshold` |
|
|
207
|
+
|
|
208
|
+
Legacy nested `falkordb.*` keys remain accepted as compatibility aliases for the
|
|
209
|
+
same `redis.*` settings. Removed top-level Redis aliases are no longer
|
|
210
|
+
supported.
|
|
211
|
+
|
|
212
|
+
## How It Works
|
|
213
|
+
|
|
214
|
+
### Injection Format
|
|
215
|
+
|
|
216
|
+
The plugin currently injects a `<session_memory>` XML envelope into the last
|
|
217
|
+
user message. This envelope is assembled from short-term memory in Redis and can
|
|
218
|
+
contain structured sections such as `<last_request>`, `<active_tasks>`,
|
|
219
|
+
`<key_decisions>`, `<files_in_play>`, `<project_rules>`, and an optional
|
|
220
|
+
`<session_snapshot>`.
|
|
221
|
+
|
|
222
|
+
When long-term memory is available, a nested `<persistent_memory>` section is
|
|
223
|
+
included with a `node_refs` attribute naming the emitted cached entities. On a
|
|
224
|
+
cold first turn or when Graphiti is unreachable, `<persistent_memory>` is simply
|
|
225
|
+
absent — the rest of the session memory is always available from short-term
|
|
226
|
+
storage in FalkorDB/Redis.
|
|
227
|
+
|
|
228
|
+
```xml
|
|
229
|
+
<session_memory source="falkordb+graphiti-cache" version="1">
|
|
230
|
+
<last_request>Continue the current task.</last_request>
|
|
231
|
+
<active_tasks><task>Implement the new feature.</task></active_tasks>
|
|
232
|
+
<key_decisions><decision>Use Redis for short-term memory.</decision></key_decisions>
|
|
233
|
+
<files_in_play><file>src/index.ts</file></files_in_play>
|
|
234
|
+
<project_rules><rule>Graphiti runs in the background only.</rule></project_rules>
|
|
235
|
+
<session_snapshot><!-- priority-tiered snapshot --></session_snapshot>
|
|
236
|
+
<persistent_memory node_refs="nodeA">
|
|
237
|
+
<!-- long-term node/episode summaries from Graphiti, optional -->
|
|
238
|
+
</persistent_memory>
|
|
239
|
+
</session_memory>
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Session Memory Preparation (`chat.message`)
|
|
243
|
+
|
|
244
|
+
On each user message the plugin assembles the current session memory from three
|
|
245
|
+
sources:
|
|
246
|
+
|
|
247
|
+
- Recent structured session events
|
|
248
|
+
- The continuously rebuilt priority-tiered snapshot
|
|
249
|
+
- Cached long-term facts from Graphiti
|
|
250
|
+
|
|
251
|
+
These are composed into a `<session_memory>` envelope and staged for the
|
|
252
|
+
transform hook. The hook-time reads are local/cache-backed only; any fresh
|
|
253
|
+
Graphiti lookup remains on the existing background refresh path and benefits the
|
|
254
|
+
next turn instead of blocking the current one.
|
|
255
|
+
|
|
256
|
+
### User Message Injection (`experimental.chat.messages.transform`)
|
|
257
|
+
|
|
258
|
+
The transform hook reads the prepared `<session_memory>` envelope and prepends
|
|
259
|
+
it to the last user message. Legacy `<memory data-uuids>` and older
|
|
260
|
+
`<persistent_memory fact_uuids>` blocks are still scrubbed and parsed for
|
|
261
|
+
compatibility, while current `<persistent_memory>` output uses `node_refs`. This
|
|
262
|
+
approach keeps the system prompt static, enabling provider-side prefix caching,
|
|
263
|
+
and avoids influencing session titles. The prepared injection is cleared after
|
|
264
|
+
use so stale context is not re-injected on subsequent LLM calls within the same
|
|
265
|
+
turn.
|
|
266
|
+
|
|
267
|
+
### Drift Detection and Background Cache Refresh
|
|
268
|
+
|
|
269
|
+
On each user message, the plugin compares the current query against the query
|
|
270
|
+
that produced the cached memory. When Jaccard similarity between the current
|
|
271
|
+
query text and cached query text drops below `driftThreshold` (default 0.5), a
|
|
272
|
+
background cache refresh is scheduled via Graphiti. The current cached context
|
|
273
|
+
is still injected immediately; the refreshed cache becomes available on the next
|
|
274
|
+
message. This trades one message of staleness for keeping most long-term memory
|
|
275
|
+
refresh work off the response-time path.
|
|
276
|
+
|
|
277
|
+
### Event Extraction and Buffering (`event`)
|
|
278
|
+
|
|
279
|
+
User and assistant messages are captured as structured `SessionEvent` objects
|
|
280
|
+
and stored in Redis (`session:{id}:events`). The plugin listens on
|
|
281
|
+
`message.part.updated` to buffer assistant text as it streams, and on
|
|
282
|
+
`message.updated` to finalize completed assistant replies.
|
|
283
|
+
|
|
284
|
+
Events are also queued for background ingestion into long-term memory:
|
|
285
|
+
|
|
286
|
+
- **On idle** (`session.idle`): buffered events are sent to Graphiti and the
|
|
287
|
+
priority-tiered snapshot is rebuilt.
|
|
288
|
+
- **Before compaction** (`session.compacted`): all pending events are sent
|
|
289
|
+
immediately so nothing is lost.
|
|
290
|
+
|
|
291
|
+
### Compaction Preservation
|
|
292
|
+
|
|
293
|
+
Compaction is handled entirely by OpenCode's native compaction mechanism. The
|
|
294
|
+
plugin ensures session continuity survives each compaction cycle:
|
|
295
|
+
|
|
296
|
+
1. **Before compaction** (`experimental.session.compacting`): The plugin injects
|
|
297
|
+
the same `<session_memory>` envelope used for chat — including the
|
|
298
|
+
priority-tiered snapshot and cached long-term facts — so the summarizer
|
|
299
|
+
preserves important knowledge. No Graphiti call is made.
|
|
300
|
+
2. **After compaction** (`session.compacted`): The snapshot is rebuilt from
|
|
301
|
+
structured events and the compaction summary is sent to Graphiti in the
|
|
302
|
+
background, ensuring knowledge survives across compaction boundaries.
|
|
303
|
+
|
|
304
|
+
Because the snapshot is rebuilt from structured events rather than raw
|
|
305
|
+
conversation text, the agent retains a coherent picture of the workstream
|
|
306
|
+
regardless of how aggressively the conversation was summarized.
|
|
307
|
+
|
|
308
|
+
### Child / Subagent Session Handling
|
|
309
|
+
|
|
310
|
+
> **Note:** This behavior intentionally diverges from
|
|
311
|
+
> [context-mode](https://github.com/mksglu/context-mode), which records subagent
|
|
312
|
+
> work as summarized tool events. This plugin promotes child sessions to
|
|
313
|
+
> first-class participants in the root session's state so that decisions, file
|
|
314
|
+
> edits, and errors from delegated work are fully visible to the parent session.
|
|
315
|
+
> See `plans/ContextOverhaul.md` §10.1 for the design rationale.
|
|
316
|
+
|
|
317
|
+
When OpenCode spawns a child session (e.g. a subagent or delegated task), the
|
|
318
|
+
plugin resolves the child's `sessionID` to the root/parent session by walking
|
|
319
|
+
the `parentID` chain. All event storage, snapshot builds, and `<session_memory>`
|
|
320
|
+
injection then operate on the canonical root session, so child activity is
|
|
321
|
+
treated identically to parent activity:
|
|
322
|
+
|
|
323
|
+
- Child prompts and responses are recorded in the same event log as the parent.
|
|
324
|
+
- The priority-tiered snapshot includes child-derived events when it is rebuilt.
|
|
325
|
+
- Future `<session_memory>` injections — for both parent and child turns —
|
|
326
|
+
reflect the combined activity of the entire session lineage.
|
|
327
|
+
- Deleting a child session removes only that child's local bookkeeping; the root
|
|
328
|
+
session's state, events, and snapshot are preserved.
|
|
329
|
+
|
|
330
|
+
This means the agent retains full continuity across delegation boundaries
|
|
331
|
+
without any special configuration.
|
|
332
|
+
|
|
333
|
+
### Project Scoping
|
|
334
|
+
|
|
335
|
+
Each project gets a unique `group_id` derived from its directory name (e.g.
|
|
336
|
+
`opencode_my-project`). Group IDs only allow letters, numbers, dashes, and
|
|
337
|
+
underscores (colons are not allowed). This ensures memories from different
|
|
338
|
+
projects stay isolated.
|
|
339
|
+
|
|
340
|
+
## Contributing
|
|
341
|
+
|
|
342
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and release
|
|
343
|
+
process. In CI, pushes to `main` publish `latest` releases, while pull requests
|
|
344
|
+
targeting `main` publish canary builds under the `canary` dist-tag.
|
|
345
|
+
|
|
346
|
+
## License
|
|
347
|
+
|
|
348
|
+
MIT
|
|
349
|
+
|
|
350
|
+
## Acknowledgements
|
|
351
|
+
|
|
352
|
+
The structured event extraction, priority-tiered snapshots, and session
|
|
353
|
+
continuity design in this plugin are inspired by
|
|
354
|
+
[context-mode](https://github.com/mksglu/context-mode) by
|
|
355
|
+
[Mert Köseoğlu](https://github.com/mksglu).
|
|
356
|
+
|
|
357
|
+
The original plugin concept is inspired by
|
|
358
|
+
[opencode-openmemory](https://github.com/happycastle114/opencode-openmemory).
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
interface Array<T> {
|
|
3
|
+
/**
|
|
4
|
+
* Returns the value of the last element in the array where predicate is true, and undefined
|
|
5
|
+
* otherwise.
|
|
6
|
+
* @param predicate find calls predicate once for each element of the array, in ascending
|
|
7
|
+
* order, until it finds one where predicate returns true. If such an element is found, find
|
|
8
|
+
* immediately returns that element value. Otherwise, find returns undefined.
|
|
9
|
+
* @param thisArg If provided, it will be used as the this value for each invocation of
|
|
10
|
+
* predicate. If it is not provided, undefined is used instead.
|
|
11
|
+
*/
|
|
12
|
+
findLast<S extends T>(predicate: (this: void, value: T, index: number, obj: T[]) => value is S, thisArg?: any): S | undefined;
|
|
13
|
+
findLast(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: any): T | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Returns the index of the last element in the array where predicate is true, and -1
|
|
16
|
+
* otherwise.
|
|
17
|
+
* @param predicate find calls predicate once for each element of the array, in ascending
|
|
18
|
+
* order, until it finds one where predicate returns true. If such an element is found,
|
|
19
|
+
* findIndex immediately returns that element index. Otherwise, findIndex returns -1.
|
|
20
|
+
* @param thisArg If provided, it will be used as the this value for each invocation of
|
|
21
|
+
* predicate. If it is not provided, undefined is used instead.
|
|
22
|
+
*/
|
|
23
|
+
findLastIndex(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: any): number;
|
|
24
|
+
}
|
|
25
|
+
interface Uint8Array {
|
|
26
|
+
/**
|
|
27
|
+
* Returns the value of the last element in the array where predicate is true, and undefined
|
|
28
|
+
* otherwise.
|
|
29
|
+
* @param predicate findLast calls predicate once for each element of the array, in descending
|
|
30
|
+
* order, until it finds one where predicate returns true. If such an element is found, findLast
|
|
31
|
+
* immediately returns that element value. Otherwise, findLast returns undefined.
|
|
32
|
+
* @param thisArg If provided, it will be used as the this value for each invocation of
|
|
33
|
+
* predicate. If it is not provided, undefined is used instead.
|
|
34
|
+
*/
|
|
35
|
+
findLast<S extends number>(predicate: (value: number, index: number, array: Uint8Array) => value is S, thisArg?: any): S | undefined;
|
|
36
|
+
findLast(predicate: (value: number, index: number, array: Uint8Array) => unknown, thisArg?: any): number | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Returns the index of the last element in the array where predicate is true, and -1
|
|
39
|
+
* otherwise.
|
|
40
|
+
* @param predicate findLastIndex calls predicate once for each element of the array, in descending
|
|
41
|
+
* order, until it finds one where predicate returns true. If such an element is found,
|
|
42
|
+
* findLastIndex immediately returns that element index. Otherwise, findLastIndex returns -1.
|
|
43
|
+
* @param thisArg If provided, it will be used as the this value for each invocation of
|
|
44
|
+
* predicate. If it is not provided, undefined is used instead.
|
|
45
|
+
*/
|
|
46
|
+
findLastIndex(predicate: (value: number, index: number, array: Uint8Array) => unknown, thisArg?: any): number;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export {};
|
|
50
|
+
declare global {
|
|
51
|
+
interface Object {
|
|
52
|
+
/**
|
|
53
|
+
* Determines whether an object has a property with the specified name.
|
|
54
|
+
* @param o An object.
|
|
55
|
+
* @param v A property name.
|
|
56
|
+
*/
|
|
57
|
+
hasOwn(o: object, v: PropertyKey): boolean;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export {};
|
|
61
|
+
declare global {
|
|
62
|
+
interface Error {
|
|
63
|
+
cause?: unknown;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export {};
|
|
67
|
+
/**
|
|
68
|
+
* Based on [import-meta-ponyfill](https://github.com/gaubee/import-meta-ponyfill),
|
|
69
|
+
* but instead of using npm to install additional dependencies,
|
|
70
|
+
* this approach manually consolidates cjs/mjs/d.ts into a single file.
|
|
71
|
+
*
|
|
72
|
+
* Note that this code might be imported multiple times
|
|
73
|
+
* (for example, both dnt.test.polyfills.ts and dnt.polyfills.ts contain this code;
|
|
74
|
+
* or Node.js might dynamically clear the cache and then force a require).
|
|
75
|
+
* Therefore, it's important to avoid redundant writes to global objects.
|
|
76
|
+
* Additionally, consider that commonjs is used alongside esm,
|
|
77
|
+
* so the two ponyfill functions are stored independently in two separate global objects.
|
|
78
|
+
*/
|
|
79
|
+
import { createRequire } from "node:module";
|
|
80
|
+
import { type URL } from "node:url";
|
|
81
|
+
declare global {
|
|
82
|
+
interface ImportMeta {
|
|
83
|
+
/** A string representation of the fully qualified module URL. When the
|
|
84
|
+
* module is loaded locally, the value will be a file URL (e.g.
|
|
85
|
+
* `file:///path/module.ts`).
|
|
86
|
+
*
|
|
87
|
+
* You can also parse the string as a URL to determine more information about
|
|
88
|
+
* how the current module was loaded. For example to determine if a module was
|
|
89
|
+
* local or not:
|
|
90
|
+
*
|
|
91
|
+
* ```ts
|
|
92
|
+
* const url = new URL(import.meta.url);
|
|
93
|
+
* if (url.protocol === "file:") {
|
|
94
|
+
* console.log("this module was loaded locally");
|
|
95
|
+
* }
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
url: string;
|
|
99
|
+
/**
|
|
100
|
+
* A function that returns resolved specifier as if it would be imported
|
|
101
|
+
* using `import(specifier)`.
|
|
102
|
+
*
|
|
103
|
+
* ```ts
|
|
104
|
+
* console.log(import.meta.resolve("./foo.js"));
|
|
105
|
+
* // file:///dev/foo.js
|
|
106
|
+
* ```
|
|
107
|
+
*
|
|
108
|
+
* @param specifier The module specifier to resolve relative to `parent`.
|
|
109
|
+
* @param parent The absolute parent module URL to resolve from.
|
|
110
|
+
* @returns The absolute (`file:`) URL string for the resolved module.
|
|
111
|
+
*/
|
|
112
|
+
resolve(specifier: string, parent?: string | URL | undefined): string;
|
|
113
|
+
/** A flag that indicates if the current module is the main module that was
|
|
114
|
+
* called when starting the program under Deno.
|
|
115
|
+
*
|
|
116
|
+
* ```ts
|
|
117
|
+
* if (import.meta.main) {
|
|
118
|
+
* // this was loaded as the main module, maybe do some bootstrapping
|
|
119
|
+
* }
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
main: boolean;
|
|
123
|
+
/** The absolute path of the current module.
|
|
124
|
+
*
|
|
125
|
+
* This property is only provided for local modules (ie. using `file://` URLs).
|
|
126
|
+
*
|
|
127
|
+
* Example:
|
|
128
|
+
* ```
|
|
129
|
+
* // Unix
|
|
130
|
+
* console.log(import.meta.filename); // /home/alice/my_module.ts
|
|
131
|
+
*
|
|
132
|
+
* // Windows
|
|
133
|
+
* console.log(import.meta.filename); // C:\alice\my_module.ts
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
filename: string;
|
|
137
|
+
/** The absolute path of the directory containing the current module.
|
|
138
|
+
*
|
|
139
|
+
* This property is only provided for local modules (ie. using `file://` URLs).
|
|
140
|
+
*
|
|
141
|
+
* * Example:
|
|
142
|
+
* ```
|
|
143
|
+
* // Unix
|
|
144
|
+
* console.log(import.meta.dirname); // /home/alice
|
|
145
|
+
*
|
|
146
|
+
* // Windows
|
|
147
|
+
* console.log(import.meta.dirname); // C:\alice
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
dirname: string;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
type NodeRequest = ReturnType<typeof createRequire>;
|
|
154
|
+
type NodeModule = NonNullable<NodeRequest["main"]>;
|
|
155
|
+
interface ImportMetaPonyfillCommonjs {
|
|
156
|
+
(require: NodeRequest, module: NodeModule): ImportMeta;
|
|
157
|
+
}
|
|
158
|
+
interface ImportMetaPonyfillEsmodule {
|
|
159
|
+
(importMeta: ImportMeta): ImportMeta;
|
|
160
|
+
}
|
|
161
|
+
interface ImportMetaPonyfill extends ImportMetaPonyfillCommonjs, ImportMetaPonyfillEsmodule {
|
|
162
|
+
}
|
|
163
|
+
export declare let import_meta_ponyfill_commonjs: ImportMetaPonyfillCommonjs;
|
|
164
|
+
export declare let import_meta_ponyfill_esmodule: ImportMetaPonyfillEsmodule;
|
|
165
|
+
export declare let import_meta_ponyfill: ImportMetaPonyfill;
|
|
166
|
+
//# sourceMappingURL=_dnt.polyfills.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_dnt.polyfills.d.ts","sourceRoot":"","sources":["../src/_dnt.polyfills.ts"],"names":[],"mappings":"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"}
|