opencode-working-memory 1.0.0 โ†’ 1.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/README.md CHANGED
@@ -18,11 +18,9 @@ Stop losing context across compactions. Stop watching your agent repeat the same
18
18
 
19
19
  ## Installation
20
20
 
21
- ### For Humans
21
+ **Option A: Let an AI agent do it**
22
22
 
23
- **Option A: Let an LLM do it**
24
-
25
- Paste this into Claude Code, Cursor, or any AI agent:
23
+ Paste this into any AI agent:
26
24
 
27
25
  ```
28
26
  Install opencode-working-memory plugin by following:
@@ -31,7 +29,7 @@ https://raw.githubusercontent.com/sdwolf4103/opencode-working-memory/main/docs/i
31
29
 
32
30
  **Option B: Manual setup**
33
31
 
34
- 1. Add to your `~/.config/opencode/opencode.json`:
32
+ Add to your `~/.config/opencode/opencode.json`:
35
33
 
36
34
  ```json
37
35
  {
@@ -39,42 +37,33 @@ https://raw.githubusercontent.com/sdwolf4103/opencode-working-memory/main/docs/i
39
37
  }
40
38
  ```
41
39
 
42
- 2. Restart OpenCode
43
-
44
- That's it! The plugin is now active.
40
+ Restart OpenCode. The plugin is downloaded and installed automatically.
45
41
 
46
42
  ### For LLM Agents
47
43
 
48
- 1. Read the OpenCode configuration file at `~/.config/opencode/opencode.json`
44
+ 1. Edit `~/.config/opencode/opencode.json`
49
45
  2. Add `"opencode-working-memory"` to the `plugin` array
50
- 3. Verify installation by checking the config file
46
+ 3. Verify the config file was saved correctly
51
47
 
52
48
  ## Quick Start
53
49
 
54
50
  The plugin works automatically once installed. No configuration needed.
55
51
 
56
- **Use the tools:**
57
-
58
- ```bash
59
- # Update persistent memory
60
- opencode run "use core_memory_update to set my goal"
52
+ **Try telling your agent:**
61
53
 
62
- # Read current memory state
63
- opencode run "use core_memory_read to show me what you remember"
64
-
65
- # Add important items to working memory
66
- opencode run "use working_memory_add to remember this file path"
67
54
  ```
68
-
69
- **The agent will automatically:**
70
- - Track memory pressure and warn when approaching compaction
71
- - Preserve important context during compaction
72
- - Clean up old tool-output cache files every 20 tool calls
73
- - Remove artifacts when sessions are deleted
55
+ Use core_memory_update to set my current goal
56
+ ```
57
+ ```
58
+ Use core_memory_read to show me what you remember
59
+ ```
60
+ ```
61
+ Use working_memory_add to remember this file path
62
+ ```
74
63
 
75
64
  ## Features
76
65
 
77
- ### ๐Ÿง  Core Memory (Phase 1)
66
+ ### ๐Ÿง  Core Memory
78
67
 
79
68
  Persistent blocks that survive conversation resets:
80
69
 
@@ -82,7 +71,7 @@ Persistent blocks that survive conversation resets:
82
71
  - **progress** (2000 chars) - What's done, in-progress, next steps
83
72
  - **context** (1500 chars) - Key file paths, conventions, patterns
84
73
 
85
- ### ๐Ÿ’ก Working Memory (Phase 3)
74
+ ### ๐Ÿ’ก Working Memory
86
75
 
87
76
  Auto-extracts and ranks important information:
88
77
 
@@ -91,7 +80,7 @@ Auto-extracts and ranks important information:
91
80
  - Exponential decay keeps memory fresh
92
81
  - FIFO limits prevent bloat
93
82
 
94
- ### ๐ŸŽฏ Memory Pressure Monitoring (Phase 4)
83
+ ### ๐ŸŽฏ Memory Pressure Monitoring
95
84
 
96
85
  Real-time token tracking from session database:
97
86
 
@@ -99,16 +88,15 @@ Real-time token tracking from session database:
99
88
  - Proactive intervention messages when pressure is high
100
89
  - Pressure-aware smart pruning (adapts compression based on pressure)
101
90
 
102
- ### ๐Ÿงน Storage Governance (Phase 5)
91
+ ### ๐Ÿงน Storage Governance
103
92
 
104
93
  Prevents unbounded disk growth:
105
94
 
106
- - **Layer 1**: Auto-cleanup on session deletion (all artifacts removed)
107
- - **Layer 2**: Active cache management (max 300 files/session, 7-day TTL)
108
- - Triggers every 20 tool calls
95
+ - Auto-cleanup on session deletion (all artifacts removed)
96
+ - Active cache management (max 300 files/session, 7-day TTL)
109
97
  - Silent background operation
110
98
 
111
- ### ๐Ÿ“Š Smart Pruning (Phase 2)
99
+ ### ๐Ÿ“Š Smart Pruning
112
100
 
113
101
  Intelligent tool output compression:
114
102
 
@@ -190,28 +178,7 @@ The plugin exposes these tools to your OpenCode agent:
190
178
 
191
179
  ## Configuration (Optional)
192
180
 
193
- The plugin works great with zero configuration. But if you want to customize:
194
-
195
- Create `~/.config/opencode/working-memory.json`:
196
-
197
- ```json
198
- {
199
- "storage_governance": {
200
- "tool_output_max_files": 300,
201
- "tool_output_max_age_ms": 604800000,
202
- "sweep_interval": 20
203
- },
204
- "memory_pressure": {
205
- "thresholds": {
206
- "moderate": 0.75,
207
- "high": 0.90,
208
- "critical": 0.95
209
- }
210
- }
211
- }
212
- ```
213
-
214
- See [Configuration Guide](docs/configuration.md) for all options.
181
+ The plugin works great with zero configuration. To customize behavior, modify the constants at the top of `index.ts`. See the [Configuration Guide](docs/configuration.md) for all tunable options.
215
182
 
216
183
  ## Requirements
217
184
 
@@ -223,20 +190,17 @@ See [Configuration Guide](docs/configuration.md) for all options.
223
190
 
224
191
  MIT License - see [LICENSE](LICENSE) file for details.
225
192
 
226
- ## Contributing
227
-
228
- Contributions welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) first.
229
-
230
193
  ## Support
231
194
 
232
195
  - ๐Ÿ“– [Documentation](docs/)
233
196
  - ๐Ÿ› [Report Issues](https://github.com/sdwolf4103/opencode-working-memory/issues)
234
- - ๐Ÿ’ฌ [Discussions](https://github.com/sdwolf4103/opencode-working-memory/discussions)
235
197
 
236
198
  ## Credits
237
199
 
238
200
  Inspired by the needs of real-world OpenCode usage and built to solve actual pain points in AI-assisted development.
239
201
 
202
+ > This project is not affiliated with or endorsed by the OpenCode team.
203
+
240
204
  ---
241
205
 
242
206
  **Made with โค๏ธ for the OpenCode community**
@@ -68,9 +68,8 @@ const POOL_MAX_ITEMS = 50; // Hard limit on pool size
68
68
 
69
69
  ```typescript
70
70
  const PRESSURE_THRESHOLDS = {
71
- moderate: 70, // Warning appears in system prompt
72
- high: 85, // Aggressive pruning activates
73
- critical: 95, // Intervention sent to agent
71
+ moderate: 75, // Warning appears in system prompt
72
+ high: 90, // Aggressive pruning activates + intervention sent
74
73
  };
75
74
  ```
76
75
 
@@ -1,90 +1,34 @@
1
1
  # Installation Guide
2
2
 
3
- ## Prerequisites
3
+ ## Quick Install
4
4
 
5
- - **OpenCode** 1.0.0 or higher
6
- - **Node.js** 18+ (for development only)
7
-
8
- ## Quick Install (For Users)
9
-
10
- ### Option 1: Install from npm (Recommended)
11
-
12
- ```bash
13
- npm install opencode-working-memory
14
- ```
15
-
16
- Then add to your `.opencode/package.json`:
17
-
18
- ```json
19
- {
20
- "plugins": [
21
- "opencode-working-memory"
22
- ]
23
- }
24
- ```
25
-
26
- ### Option 2: Install from GitHub
27
-
28
- Add to your `.opencode/package.json`:
5
+ Add to your `~/.config/opencode/opencode.json`:
29
6
 
30
7
  ```json
31
8
  {
32
- "dependencies": {
33
- "opencode-working-memory": "github:sdwolf4103/opencode-working-memory"
34
- },
35
- "plugins": [
36
- "opencode-working-memory"
37
- ]
9
+ "plugin": ["opencode-working-memory"]
38
10
  }
39
11
  ```
40
12
 
41
- Then run:
42
-
43
- ```bash
44
- cd .opencode
45
- npm install
46
- ```
47
-
48
- ### Option 3: Local Development Install
49
-
50
- Clone the repository:
13
+ Restart OpenCode. The plugin is downloaded and installed automatically โ€” no `npm install` needed.
51
14
 
52
- ```bash
53
- git clone https://github.com/sdwolf4103/opencode-working-memory.git
54
- cd opencode-working-memory
55
- npm install
56
- ```
15
+ > **Note**: The correct key is `plugin` (singular), not `plugins`.
57
16
 
58
- Link to your OpenCode project:
17
+ ## For LLM Agents
59
18
 
60
- ```bash
61
- cd /path/to/your/project/.opencode
62
- npm link /path/to/opencode-working-memory
63
- ```
64
-
65
- Add to `.opencode/package.json`:
66
-
67
- ```json
68
- {
69
- "plugins": [
70
- "opencode-working-memory"
71
- ]
72
- }
73
- ```
19
+ 1. Edit `~/.config/opencode/opencode.json`
20
+ 2. Add `"opencode-working-memory"` to the `plugin` array
21
+ 3. Verify the config file was saved correctly
74
22
 
75
23
  ## Verification
76
24
 
77
- After installation, start an OpenCode session and run:
25
+ After restarting OpenCode, ask your agent:
78
26
 
79
27
  ```
80
- core_memory_update goal "Test installation"
28
+ Use core_memory_read to show me what you remember
81
29
  ```
82
30
 
83
- You should see a success message. Check `.opencode/memory-core/` for the session file.
84
-
85
- ## Configuration
86
-
87
- The plugin works out-of-the-box with sensible defaults. For advanced configuration, see [configuration.md](./configuration.md).
31
+ If the tool responds, the plugin is active.
88
32
 
89
33
  ## Troubleshooting
90
34
 
@@ -92,10 +36,10 @@ The plugin works out-of-the-box with sensible defaults. For advanced configurati
92
36
 
93
37
  **Symptom**: No `core_memory_update` tool available
94
38
 
95
- **Solution**:
96
- 1. Check `.opencode/package.json` includes plugin in `"plugins": []` array
97
- 2. Verify `npm install` completed successfully
98
- 3. Restart OpenCode session
39
+ **Solution**:
40
+ 1. Check `~/.config/opencode/opencode.json` uses `"plugin"` (not `"plugins"`)
41
+ 2. Restart OpenCode to trigger automatic installation
42
+ 3. Check OpenCode logs for any download errors
99
43
 
100
44
  ### Memory Files Not Created
101
45
 
@@ -103,26 +47,22 @@ The plugin works out-of-the-box with sensible defaults. For advanced configurati
103
47
 
104
48
  **Solution**:
105
49
  1. Ensure OpenCode has write permissions in project directory
106
- 2. Check plugin hooks are registered (look for "Working Memory Plugin" in session logs)
107
- 3. Trigger memory operations (e.g., use `core_memory_update` tool)
50
+ 2. Trigger memory operations (e.g., use `core_memory_update` tool)
108
51
 
109
52
  ### Type Errors During Development
110
53
 
111
- **Symptom**: TypeScript errors when modifying plugin
54
+ **Symptom**: TypeScript errors when modifying the plugin source
112
55
 
113
56
  **Solution**:
114
- 1. Ensure `@opencode-ai/plugin` is installed: `npm install @opencode-ai/plugin`
115
- 2. Run type checking: `npx tsc --noEmit`
57
+ 1. Run `npm install` to install dev dependencies
58
+ 2. Run `npm run typecheck` to check for errors
116
59
  3. See [AGENTS.md](../AGENTS.md) for code style guidelines
117
60
 
118
61
  ## Uninstallation
119
62
 
120
- ```bash
121
- cd .opencode
122
- npm uninstall opencode-working-memory
123
- ```
63
+ Remove `"opencode-working-memory"` from the `plugin` array in `~/.config/opencode/opencode.json`.
124
64
 
125
- Remove from `.opencode/package.json` plugins array. Memory files in `.opencode/memory-*` will persist unless manually deleted.
65
+ Memory files in `.opencode/memory-*` will persist unless manually deleted.
126
66
 
127
67
  ## Next Steps
128
68
 
package/index.ts CHANGED
@@ -1,16 +1,11 @@
1
1
  /**
2
2
  * Working Memory Plugin for OpenCode
3
- *
4
- * Provides a three-tier memory system to delay/avoid compaction:
3
+ *
4
+ * Four-tier memory architecture:
5
5
  * 1. Core Memory - Persistent goal/progress/context blocks (always in-context)
6
6
  * 2. Working Memory - Auto-managed session-relevant information
7
7
  * 3. Smart Pruning - Content-aware tool output compression
8
8
  * 4. Memory Pressure Monitoring - Context usage tracking with adaptive warnings
9
- *
10
- * Phase 1: Core Memory Foundation (MVP) - โœ… COMPLETED
11
- * Phase 2: Smart Pruning System - โœ… COMPLETED
12
- * Phase 3: Working Memory Auto-Management - โœ… COMPLETED
13
- * Phase 4: Memory Pressure Monitoring - โœ… COMPLETED
14
9
  */
15
10
 
16
11
  import type { Plugin } from "@opencode-ai/plugin";
@@ -46,7 +41,7 @@ const CORE_MEMORY_LIMITS = {
46
41
  };
47
42
 
48
43
  // ============================================================================
49
- // Phase 2: Smart Pruning Types
44
+ // Smart Pruning Types
50
45
  // ============================================================================
51
46
 
52
47
  type PruningStrategy =
@@ -72,7 +67,7 @@ type CachedToolOutput = {
72
67
  };
73
68
 
74
69
  // ============================================================================
75
- // Phase 3: Working Memory Types (Slot-based Architecture)
70
+ // Working Memory Types (Slot-based Architecture)
76
71
  // ============================================================================
77
72
 
78
73
  type WorkingMemory = {
@@ -128,7 +123,7 @@ const WORKING_MEMORY_LIMITS = {
128
123
  };
129
124
 
130
125
  // ============================================================================
131
- // Storage Governance (Layer 1 + Layer 2)
126
+ // Storage Governance
132
127
  // ============================================================================
133
128
 
134
129
  const STORAGE_GOVERNANCE = {
@@ -138,7 +133,7 @@ const STORAGE_GOVERNANCE = {
138
133
  };
139
134
 
140
135
  // ============================================================================
141
- // Phase 4: Memory Pressure Monitoring
136
+ // Memory Pressure Monitoring
142
137
  // ============================================================================
143
138
 
144
139
  type PressureLevel = "safe" | "moderate" | "high";
@@ -169,7 +164,6 @@ type ModelPressureInfo = {
169
164
  updatedAt: string;
170
165
  };
171
166
 
172
- // Compaction tracking (preserved from Phase 4 initial work)
173
167
  type CompactionLog = {
174
168
  sessionID: string;
175
169
  compactionCount: number;
@@ -501,12 +495,11 @@ async function updateCoreMemoryBlock(
501
495
  }
502
496
 
503
497
  // ============================================================================
504
- // Storage Governance Functions (Layer 1 + Layer 2)
498
+ // Storage Governance Functions
505
499
  // ============================================================================
506
500
 
507
501
  /**
508
- * Layer 1: Clean up all artifacts for a deleted session
509
- * Called when session.deleted event is received
502
+ * Clean up all artifacts for a deleted session.
510
503
  */
511
504
  async function cleanupSessionArtifacts(
512
505
  directory: string,
@@ -532,9 +525,9 @@ async function cleanupSessionArtifacts(
532
525
  }
533
526
 
534
527
  /**
535
- * Layer 2: Sweep tool-output cache for a session
536
- * Remove files older than TTL and enforce max file count
537
- * Returns number of files deleted
528
+ * Sweep tool-output cache for a session.
529
+ * Removes files older than TTL and enforces max file count.
530
+ * Returns number of files deleted.
538
531
  */
539
532
  async function sweepToolOutputCache(
540
533
  directory: string,
@@ -606,7 +599,7 @@ async function sweepToolOutputCache(
606
599
  }
607
600
 
608
601
  // ============================================================================
609
- // Phase 2: Smart Pruning System
602
+ // Smart Pruning System
610
603
  // ============================================================================
611
604
 
612
605
  /**
@@ -775,7 +768,7 @@ async function getCachedToolOutput(
775
768
  }
776
769
 
777
770
  // ============================================================================
778
- // Phase 3: Working Memory Auto-Management
771
+ // Working Memory Auto-Management
779
772
  // ============================================================================
780
773
 
781
774
  /**
@@ -1057,9 +1050,7 @@ function getTopItemsForPrompt(
1057
1050
  }
1058
1051
 
1059
1052
  /**
1060
- * Compress file paths to save space in system prompt
1061
- * /Users/sd_wo/opencode/packages/opencode/src/foo.ts โ†’ ~/opencode/pkg/opencode/src/foo.ts
1062
- * /Users/sd_wo/work/opencode-plugins/.opencode/plugins/foo.ts โ†’ ~/work/oc-plugins/.opencode/plugins/foo.ts
1053
+ * Compress file paths to save space in system prompt.
1063
1054
  */
1064
1055
  function compressPath(content: string): string {
1065
1056
  const homeDir = process.env.HOME || '/Users/' + (process.env.USER || 'user');
@@ -1131,7 +1122,7 @@ Recent session context (auto-managed, sorted by relevance):
1131
1122
 
1132
1123
  ${sections.join("\n\n")}
1133
1124
 
1134
- (${totalItems} items shown, updated: ${new Date(memory.updatedAt).toLocaleTimeString()})
1125
+ (${totalItems} items shown)
1135
1126
  </working_memory>
1136
1127
  `.trim();
1137
1128
  }
@@ -1145,7 +1136,7 @@ function getWorkingMemoryItemCount(memory: WorkingMemory): number {
1145
1136
  }
1146
1137
 
1147
1138
  // ============================================================================
1148
- // Phase 4: Compaction Tracking and State Preservation
1139
+ // Compaction Tracking
1149
1140
  // ============================================================================
1150
1141
 
1151
1142
  /**
@@ -1207,14 +1198,13 @@ async function recordCompaction(
1207
1198
  // ============================================================================
1208
1199
 
1209
1200
  /**
1210
- * Calculate usable tokens using OpenCode's exact compaction formula
1211
- * Reference: packages/opencode/src/session/compaction.ts:32-48
1201
+ * Calculate usable tokens using OpenCode's compaction formula.
1212
1202
  */
1213
1203
  function calculateUsableTokens(model: {
1214
1204
  limit: { context: number; input?: number; output: number };
1215
1205
  }): number {
1216
- const OUTPUT_TOKEN_MAX = 32_000; // From transform.ts:21
1217
- const COMPACTION_BUFFER = 20_000; // From compaction.ts:33
1206
+ const OUTPUT_TOKEN_MAX = 32_000;
1207
+ const COMPACTION_BUFFER = 20_000;
1218
1208
 
1219
1209
  const maxOutputTokens = Math.min(
1220
1210
  model.limit.output || OUTPUT_TOKEN_MAX,
@@ -1222,7 +1212,6 @@ function calculateUsableTokens(model: {
1222
1212
  );
1223
1213
  const reserved = Math.min(COMPACTION_BUFFER, maxOutputTokens);
1224
1214
 
1225
- // Match compaction.ts:42-47
1226
1215
  const usable = model.limit.input
1227
1216
  ? model.limit.input - reserved
1228
1217
  : model.limit.context - maxOutputTokens;
@@ -1231,11 +1220,7 @@ function calculateUsableTokens(model: {
1231
1220
  }
1232
1221
 
1233
1222
  /**
1234
- * Calculate pressure level based on current tokens and usable limit
1235
- *
1236
- * Thresholds:
1237
- * - 0.75 (75%): moderate - show reminder in prompt
1238
- * - 0.9 (90%): high - send intervention message
1223
+ * Calculate pressure level based on current tokens and usable limit.
1239
1224
  */
1240
1225
  function calculatePressureLevel(
1241
1226
  currentTokens: number,
@@ -1334,18 +1319,14 @@ async function loadModelPressureInfo(
1334
1319
  }
1335
1320
 
1336
1321
  /**
1337
- * Calculate total tokens by querying OpenCode's session database
1338
- * This is more reliable than relying on hook-provided messages
1339
- *
1340
- * Note: Only looks at last 10 messages to avoid stale data from before compaction
1322
+ * Calculate total tokens by querying OpenCode's session database.
1341
1323
  */
1342
1324
  async function calculateTotalTokensFromDB(sessionID: string): Promise<number> {
1343
1325
  try {
1344
1326
  const { execSync } = await import("child_process");
1345
1327
  const dbPath = join(process.env.HOME || "~", ".local/share/opencode/opencode.db");
1346
1328
 
1347
- // Get tokens.total from most recent assistant message (last 10 to be safe)
1348
- // Use MAX to handle edge cases, but limit to recent messages to avoid stale pre-compaction data
1329
+ // Get tokens.total from most recent assistant message
1349
1330
  const query = `
1350
1331
  SELECT json_extract(data, '$.tokens.total') as total
1351
1332
  FROM message
@@ -1365,11 +1346,7 @@ async function calculateTotalTokensFromDB(sessionID: string): Promise<number> {
1365
1346
  }
1366
1347
 
1367
1348
  /**
1368
- * Generate pressure warning text for system prompt injection
1369
- *
1370
- * Design principles:
1371
- * - MODERATE (75%): gentle nudge, no interruption
1372
- * - HIGH (90%): actionable commands, pause and persist state
1349
+ * Generate pressure warning text for system prompt injection.
1373
1350
  */
1374
1351
  function generatePressureWarning(info: ModelPressureInfo): string {
1375
1352
  const { current, calculated } = info;
@@ -1387,13 +1364,8 @@ function generatePressureWarning(info: ModelPressureInfo): string {
1387
1364
  }
1388
1365
 
1389
1366
  /**
1390
- * Send proactive intervention message when HIGH pressure detected (90%)
1391
- *
1392
- * This sends an independent system message to the session immediately, so the agent
1393
- * receives the task in the queue without interrupting current work. The agent will
1394
- * process it automatically when available.
1395
- *
1396
- * Design: Use promptAsync() which returns 204 immediately, non-blocking.
1367
+ * Send a proactive intervention message when HIGH pressure (90%) is detected.
1368
+ * Uses promptAsync() which returns immediately (non-blocking).
1397
1369
  */
1398
1370
  async function sendPressureInterventionMessage(
1399
1371
  client: any,
@@ -1421,17 +1393,14 @@ REQUIRED ACTIONS:
1421
1393
  After completing these actions, you may resume your current task.`;
1422
1394
 
1423
1395
  try {
1424
- // Use promptAsync to send message without waiting for response
1425
1396
  await client.session.promptAsync({
1426
1397
  path: { id: sessionID },
1427
1398
  body: {
1428
1399
  parts: [{
1429
1400
  type: "text",
1430
- // Send actionable content directly (not log-style placeholder)
1431
1401
  text: systemPrompt,
1432
1402
  }],
1433
- // Keep system unset so the intervention is visible as a normal prompt
1434
- noReply: false, // We want agent to respond with actions
1403
+ noReply: false,
1435
1404
  },
1436
1405
  });
1437
1406
  } catch (error) {
@@ -1440,36 +1409,32 @@ After completing these actions, you may resume your current task.`;
1440
1409
  }
1441
1410
 
1442
1411
  /**
1443
- * Get pressure-aware pruning config based on current memory pressure
1444
- * HYPER-AGGRESSIVE MODE: pressure >= 0.90 enforces strict limits
1412
+ * Get pressure-aware pruning config based on current memory pressure.
1445
1413
  */
1446
1414
  function getPressureAwarePruningConfig(pressure: number): {
1447
1415
  maxLines: number;
1448
1416
  maxChars: number;
1449
1417
  aggressiveTruncation: boolean;
1450
1418
  } {
1451
- // HIGH (>= 90%): Hyper-Aggressive Mode
1452
1419
  if (pressure >= 0.90) {
1453
1420
  return {
1454
- maxLines: 2000, // Hard limit: 2000 lines max
1455
- maxChars: 100_000, // ~25k tokens max per tool output
1456
- aggressiveTruncation: true, // Force truncation, no exceptions
1421
+ maxLines: 2000,
1422
+ maxChars: 100_000,
1423
+ aggressiveTruncation: true,
1457
1424
  };
1458
1425
  }
1459
1426
 
1460
- // MODERATE (>= 75%): Aggressive Mode
1461
1427
  if (pressure >= 0.75) {
1462
1428
  return {
1463
1429
  maxLines: 5000,
1464
- maxChars: 200_000, // ~50k tokens max
1430
+ maxChars: 200_000,
1465
1431
  aggressiveTruncation: true,
1466
1432
  };
1467
1433
  }
1468
1434
 
1469
- // SAFE (< 75%): Normal Mode
1470
1435
  return {
1471
1436
  maxLines: 10_000,
1472
- maxChars: 400_000, // ~100k tokens max
1437
+ maxChars: 400_000,
1473
1438
  aggressiveTruncation: false,
1474
1439
  };
1475
1440
  }
@@ -1498,18 +1463,9 @@ ${context.value || "[No project context set - add relevant file paths, conventio
1498
1463
  </context>
1499
1464
 
1500
1465
  IMPORTANT: These blocks persist across conversation resets and compaction.
1501
- Update them regularly using core_memory_update tool when:
1502
- - Goals change or new objectives are identified
1503
- - Significant progress is made or tasks are completed
1504
- - Important project context is discovered (file structures, patterns, conventions)
1505
-
1506
- When memory blocks approach their character limits, compress or rephrase content.
1507
-
1508
- **Usage Discipline** (see Core Memory Usage Guidelines above for details):
1509
- - goal: ONE specific task, not project-wide goals
1510
- - progress: Checklist format, NO line numbers/commit hashes/API signatures
1511
- - context: ONLY files you're currently working on, NO type definitions/function signatures
1512
- - NEVER store: API docs, library types, function signatures (read source instead)
1466
+ Update them regularly using core_memory_update tool. When blocks approach their character limits, compress or rephrase content.
1467
+
1468
+ To mark decisions for automatic capture into working memory, write inline: [Decision: chose X over Y because Z]
1513
1469
  </core_memory>
1514
1470
  `.trim();
1515
1471
  }
@@ -1523,98 +1479,38 @@ export default async function WorkingMemoryPlugin(
1523
1479
  ): Promise<ReturnType<Plugin>> {
1524
1480
  const { directory, client } = input;
1525
1481
 
1482
+ // Cache for sub-agent detection โ€” avoids repeated API calls per session.
1483
+ // Maps sessionID โ†’ parentID (string) or null (root session).
1484
+ const sessionParentCache = new Map<string, string | null>();
1485
+
1486
+ async function isSubAgent(sessionID: string): Promise<boolean> {
1487
+ if (sessionParentCache.has(sessionID)) {
1488
+ return sessionParentCache.get(sessionID) !== null;
1489
+ }
1490
+ try {
1491
+ const result = await client.session.get({ path: { id: sessionID } });
1492
+ const parentID = result.data?.parentID ?? null;
1493
+ sessionParentCache.set(sessionID, parentID);
1494
+ return parentID !== null;
1495
+ } catch {
1496
+ // If we can't determine, assume it's NOT a sub-agent (safe default).
1497
+ sessionParentCache.set(sessionID, null);
1498
+ return false;
1499
+ }
1500
+ }
1501
+
1526
1502
  return {
1527
- // ========================================================================
1528
- // Phase 1: Inject Core Memory and Working Memory into System Prompt
1529
- // Phase 4: Inject Memory Pressure Warnings & Calculate Tokens from DB
1530
- // Phase 4.5: Proactive Pressure Intervention (NEW)
1531
- // Phase 5: Core Memory Usage Guidelines (AGENTS.md Enhancement)
1532
- //
1533
- // Dual-System Approach:
1534
- // 1. PASSIVE WARNING (existing): Injected into next turn's system prompt
1535
- // - Always present as reminder in system context
1536
- // - 1-turn delay but persistent
1537
- //
1538
- // 2. PROACTIVE INTERVENTION (new): Immediate async message sent to queue
1539
- // - No delay, sent immediately when HIGH (90%) detected
1540
- // - Agent processes when available (non-blocking)
1541
- // - Only sent when pressure level increases (avoids spam)
1542
- //
1543
- // 3. USAGE GUIDELINES (new): Injected after AGENTS.md, before core_memory
1544
- // - Teaches agent how to use core_memory blocks correctly
1545
- // - Prevents storing API docs/type definitions in memory
1546
- // - Ensures goal/progress/context stay focused on current task
1547
- // ========================================================================
1503
+ // Inject pressure warnings, core memory, and working memory into the system prompt each turn.
1504
+ // Core memory usage guidelines are in the core_memory_update tool description instead.
1548
1505
  "experimental.chat.system.transform": async (hookInput, output) => {
1549
1506
  const { sessionID, model } = hookInput;
1550
1507
  if (!sessionID) return;
1551
1508
 
1552
- // Phase 5: Inject Core Memory Usage Guidelines
1553
- // This enhances AGENTS.md (if exists) with plugin-specific instructions
1554
- // Inserted early so it's read before agent sees <core_memory> block
1555
- const coreMemoryGuidelines = `
1556
- # Core Memory Usage Guidelines
1557
-
1558
- The Working Memory Plugin provides persistent core_memory blocks. **USE THEM CORRECTLY**:
1559
-
1560
- ## goal block (1000 chars)
1561
- **Purpose**: ONE specific task you're working on RIGHT NOW
1562
-
1563
- โœ… **GOOD Examples**:
1564
- - "Fix pruning bug where items with relevanceScore <0.01 are incorrectly excluded"
1565
- - "Add new tool: working_memory_search to query pool items by keyword"
1566
- - "Investigate why pressure warnings not showing in system prompt"
1567
-
1568
- โŒ **BAD Examples**:
1569
- - "Complete Phase 1-4 development and testing" (too broad, likely already done)
1570
- - "Build a working memory system for OpenCode" (project-level goal, not task-level)
1571
-
1572
- ## progress block (2000 chars)
1573
- **Purpose**: Checklist of done/in-progress/blocked items + key decisions
1574
-
1575
- โœ… **GOOD Examples**:
1576
- - "โœ… Found bug in applyDecay() line 856\\nโณ Testing fix with gamma=0.85\\nโ“ Need to verify edge case: score=0"
1577
- - "โœ… Phase 1-3 complete\\nโณ Phase 4 intervention testing\\nโš ๏ธ BLOCKED: Need promptAsync docs"
1578
-
1579
- โŒ **BAD Examples**:
1580
- - "Function sendPressureInterventionMessage() @ working-memory.ts:L1286-1354" (line numbers useless after edits)
1581
- - "Commit 2f42f1b implemented promptAsync integration" (commit hash irrelevant)
1582
- - "API: client.session.promptAsync({ path: {id}, body: {...} })" (API signature, not progress)
1583
-
1584
- ## context block (1500 chars)
1585
- **Purpose**: Files you're CURRENTLY editing + key patterns/conventions
1586
-
1587
- โœ… **GOOD Examples**:
1588
- - "Editing: .opencode/plugins/working-memory.ts (main plugin, 1706 lines)\\nRelated: WORKING_MEMORY.md, TEST_PHASE4.md"
1589
- - "Key paths: .opencode/memory-core/ (persistent blocks), memory-working/ (session data)"
1590
- - "Pattern: All async file ops use mkdir({recursive:true}) before writeFile"
1591
-
1592
- โŒ **BAD Examples**:
1593
- - "OpenCode SDK types: TextPartInput = { type: 'text', text: string, synthetic?: boolean }" (type definition)
1594
- - "Function signature: async function loadCoreMemory(directory: string, sessionID: string): Promise<CoreMemory | null>" (function signature)
1595
- - "Method client.session.promptAsync() returns 204 No Content" (API behavior, read docs instead)
1596
-
1597
- ## โš ๏ธ NEVER Store in Core Memory
1598
- - API documentation (read source/docs when needed)
1599
- - Type definitions from libraries (import them)
1600
- - Function signatures (read source code)
1601
- - Implementation details (belong in code comments)
1602
- - Completed goals (clear them immediately)
1603
-
1604
- ## โœ… Update Core Memory Immediately When
1605
- - **Starting new task**: Clear old goal, set new specific goal
1606
- - **Making progress**: Update progress checklist (keep concise)
1607
- - **Switching files**: Update context with current working files
1608
- - **Task completed**: Clear goal/progress, set next task
1609
- - **Approaching char limit**: Compress or remove outdated info
1509
+ // Sub-agents are short-lived โ€” skip entire memory system.
1510
+ if (await isSubAgent(sessionID)) return;
1610
1511
 
1611
- **Remember**: Core Memory is your **working scratchpad**, not a reference manual.
1612
- `.trim();
1613
-
1614
- output.system.push(coreMemoryGuidelines);
1615
-
1616
- // Phase 4: Check for memory pressure and inject warning
1617
- // Skip warning if model just changed (avoids false alarms with different limits)
1512
+ // Check for memory pressure and inject warning into system prompt.
1513
+ // Skip if model just changed (avoids false alarms with different limits).
1618
1514
  const prevPressure = await loadModelPressureInfo(directory, sessionID);
1619
1515
  const modelChanged = model && prevPressure && prevPressure.modelID !== model.id;
1620
1516
 
@@ -1625,7 +1521,7 @@ The Working Memory Plugin provides persistent core_memory blocks. **USE THEM COR
1625
1521
  }
1626
1522
  }
1627
1523
 
1628
- // Phase 4: Calculate current token usage from DB and update pressure
1524
+ // Calculate current token usage from DB and update pressure info.
1629
1525
  if (model) {
1630
1526
  const totalTokens = await calculateTotalTokensFromDB(sessionID);
1631
1527
 
@@ -1640,14 +1536,12 @@ The Working Memory Plugin provides persistent core_memory blocks. **USE THEM COR
1640
1536
  totalTokens
1641
1537
  );
1642
1538
 
1643
- // Save for next turn's warning injection
1539
+ // Save for next turn's warning injection.
1644
1540
  await saveModelPressureInfo(directory, updatedPressure);
1645
1541
 
1646
- // Phase 4.5: Proactive Intervention - Send immediate message if HIGH (90%)
1647
- // This is better than waiting for next turn's passive warning
1648
- // The message goes into the queue and agent processes it when available
1542
+ // Send proactive intervention if pressure just crossed into HIGH.
1649
1543
  if (updatedPressure.current.level === "high") {
1650
- // Only send if pressure increased from previous level (avoid spam)
1544
+ // Only send if pressure just escalated (avoid repeated spam).
1651
1545
  const shouldSend = !prevPressure ||
1652
1546
  prevPressure.current.level === "safe" ||
1653
1547
  prevPressure.current.level === "moderate";
@@ -1658,7 +1552,7 @@ The Working Memory Plugin provides persistent core_memory blocks. **USE THEM COR
1658
1552
  }
1659
1553
  }
1660
1554
 
1661
- // Phase 1: Core memory
1555
+ // Core memory
1662
1556
  const coreMemory = await loadCoreMemory(directory, sessionID);
1663
1557
  if (coreMemory) {
1664
1558
  const hasContent =
@@ -1672,7 +1566,7 @@ The Working Memory Plugin provides persistent core_memory blocks. **USE THEM COR
1672
1566
  }
1673
1567
  }
1674
1568
 
1675
- // Phase 1: Working memory
1569
+ // Working memory
1676
1570
  const workingMemory = await loadWorkingMemory(directory, sessionID);
1677
1571
  if (workingMemory && getWorkingMemoryItemCount(workingMemory) > 0) {
1678
1572
  const workingPrompt = renderWorkingMemoryPrompt(workingMemory);
@@ -1682,15 +1576,14 @@ The Working Memory Plugin provides persistent core_memory blocks. **USE THEM COR
1682
1576
  }
1683
1577
  },
1684
1578
 
1685
- // ========================================================================
1686
- // Phase 2 & 3: Cache Tool Outputs and Auto-Extract to Working Memory
1687
- // Storage Governance Layer 2: Tool Output Cache Sweep Trigger
1688
- // ========================================================================
1579
+ // Cache tool outputs, auto-extract to working memory, and sweep cache periodically.
1689
1580
  "tool.execute.after": async (hookInput, hookOutput) => {
1690
1581
  const { sessionID, callID, tool: toolName, args } = hookInput;
1691
1582
  const { output: toolOutput } = hookOutput;
1692
1583
 
1693
- // Phase 2: Cache the full output for later smart pruning
1584
+ // Sub-agents don't need working memory tracking.
1585
+ if (await isSubAgent(sessionID)) return;
1586
+
1694
1587
  await cacheToolOutput(directory, {
1695
1588
  callID,
1696
1589
  sessionID,
@@ -1699,25 +1592,25 @@ The Working Memory Plugin provides persistent core_memory blocks. **USE THEM COR
1699
1592
  timestamp: Date.now(),
1700
1593
  });
1701
1594
 
1702
- // Phase 3: Auto-extract to working memory
1703
1595
  const extractedItems = extractFromToolOutput(toolName, toolOutput);
1704
1596
  for (const item of extractedItems) {
1705
1597
  await addToWorkingMemory(directory, sessionID, item);
1706
1598
  }
1707
1599
 
1708
- // Storage Governance Layer 2: Sweep tool-output cache every N calls
1600
+ // Sweep tool-output cache every N tool calls.
1709
1601
  const memory = await loadWorkingMemory(directory, sessionID);
1710
1602
  if (memory && memory.eventCounter % STORAGE_GOVERNANCE.sweepInterval === 0) {
1711
1603
  await sweepToolOutputCache(directory, sessionID);
1712
1604
  }
1713
1605
  },
1714
1606
 
1715
- // ========================================================================
1716
- // Phase 2: Apply Smart Pruning to Messages (Pressure-Aware)
1717
- // ========================================================================
1607
+ // Apply smart pruning to compacted tool outputs (pressure-aware).
1718
1608
  "experimental.chat.messages.transform": async (hookInput, output) => {
1719
1609
  const sessionID = output.messages[0]?.info?.sessionID || "";
1720
1610
 
1611
+ // Sub-agents don't need smart pruning.
1612
+ if (sessionID && await isSubAgent(sessionID)) return;
1613
+
1721
1614
  // Load current pressure info to get pressure-aware pruning config
1722
1615
  const currentPressure = await loadModelPressureInfo(directory, sessionID);
1723
1616
  const pressureLevel = currentPressure?.current?.pressure || 0;
@@ -1754,11 +1647,32 @@ The Working Memory Plugin provides persistent core_memory blocks. **USE THEM COR
1754
1647
  }
1755
1648
  },
1756
1649
 
1757
- // ========================================================================
1758
- // Storage Governance Layer 1: Session Deletion Event Handler
1759
- // ========================================================================
1650
+ // Auto-capture [Decision: ...] markers from agent responses.
1651
+ "experimental.text.complete": async (hookInput, output) => {
1652
+ const { sessionID } = hookInput;
1653
+ if (!sessionID) return;
1654
+
1655
+ // Sub-agents are short-lived โ€” skip decision tracking.
1656
+ if (await isSubAgent(sessionID)) return;
1657
+
1658
+ // Extract all [Decision: ...] markers from the completed text.
1659
+ const matches = output.text.matchAll(/\[Decision:\s*([^\]]+)\]/gi);
1660
+ for (const match of matches) {
1661
+ const description = match[1].trim();
1662
+ if (!description) continue;
1663
+
1664
+ await addToWorkingMemory(directory, sessionID, {
1665
+ type: "decision",
1666
+ content: description,
1667
+ source: "auto:text",
1668
+ timestamp: Date.now(),
1669
+ mentions: 1,
1670
+ });
1671
+ }
1672
+ },
1673
+
1674
+ // Clean up all session artifacts on session deletion.
1760
1675
  event: async ({ event }) => {
1761
- // Listen for session.deleted events and cleanup all artifacts
1762
1676
  if (event.type === "session.deleted") {
1763
1677
  const sessionID = event.properties?.info?.id;
1764
1678
  if (sessionID) {
@@ -1767,12 +1681,13 @@ The Working Memory Plugin provides persistent core_memory blocks. **USE THEM COR
1767
1681
  }
1768
1682
  },
1769
1683
 
1770
- // ========================================================================
1771
- // Phase 4: Preserve State Before Compaction
1772
- // ========================================================================
1684
+ // Preserve working memory state before compaction.
1773
1685
  "experimental.session.compacting": async (hookInput, output) => {
1774
1686
  const { sessionID } = hookInput;
1775
1687
 
1688
+ // Sub-agents don't need compaction support.
1689
+ if (await isSubAgent(sessionID)) return;
1690
+
1776
1691
  // Preserve only the most relevant working memory items
1777
1692
  const preservedItems = await preserveRelevantItems(directory, sessionID, 0.5);
1778
1693
 
@@ -1808,7 +1723,7 @@ The Working Memory Plugin provides persistent core_memory blocks. **USE THEM COR
1808
1723
  }
1809
1724
  }
1810
1725
 
1811
- // SSOT Bridge: Inject OpenCode native Todos from DB into compaction context
1726
+ // Inject pending OpenCode todos into compaction context.
1812
1727
  try {
1813
1728
  const { execSync } = await import("child_process");
1814
1729
  const dbPath = join(process.env.HOME || "~", ".local/share/opencode/opencode.db");
@@ -1849,9 +1764,7 @@ The Working Memory Plugin provides persistent core_memory blocks. **USE THEM COR
1849
1764
  }
1850
1765
  },
1851
1766
 
1852
- // ========================================================================
1853
1767
  // Tools
1854
- // ========================================================================
1855
1768
  tool: {
1856
1769
  core_memory_update: tool({
1857
1770
  description: `Update persistent core memory blocks that survive compaction.
@@ -1866,7 +1779,48 @@ Operations:
1866
1779
  - append: Add content to the end of the block (automatically adds newline)
1867
1780
 
1868
1781
  These blocks are ALWAYS visible to you in every message, even after compaction.
1869
- Update them regularly to maintain continuity across long conversations.`,
1782
+ Update them regularly to maintain continuity across long conversations.
1783
+
1784
+ ---
1785
+
1786
+ ## Usage Guidelines
1787
+
1788
+ ### goal block (1000 chars)
1789
+ **Purpose**: ONE specific task you're working on RIGHT NOW
1790
+
1791
+ โœ… GOOD: "Fix pruning bug where items with relevanceScore <0.01 are incorrectly excluded"
1792
+ โœ… GOOD: "Add new tool: working_memory_search to query pool items by keyword"
1793
+ โŒ BAD: "Complete Phase 1-4 development and testing" (too broad, likely already done)
1794
+ โŒ BAD: "Build a working memory system for OpenCode" (project-level goal, not task-level)
1795
+
1796
+ ### progress block (2000 chars)
1797
+ **Purpose**: Checklist of done/in-progress/blocked items + key decisions
1798
+
1799
+ โœ… GOOD: "โœ… Found bug in applyDecay()\\nโณ Testing fix with gamma=0.85\\nโ“ Need to verify edge case"
1800
+ โœ… GOOD: "โœ… Phase 1-3 complete\\nโณ Phase 4 intervention testing\\nโš ๏ธ BLOCKED: Need promptAsync docs"
1801
+ โŒ BAD: "Function foo() @ file.ts:L1286-1354" (line numbers useless after edits)
1802
+ โŒ BAD: "Commit 2f42f1b implemented X" (commit hash irrelevant)
1803
+ โŒ BAD: "API: client.session.promptAsync({ ... })" (API signatures belong in source)
1804
+
1805
+ ### context block (1500 chars)
1806
+ **Purpose**: Files you're CURRENTLY editing + key patterns/conventions
1807
+
1808
+ โœ… GOOD: "Editing: src/plugin.ts\\nKey paths: .opencode/memory-core/ (persistent blocks)"
1809
+ โœ… GOOD: "Pattern: All async file ops use mkdir({recursive:true}) before writeFile"
1810
+ โŒ BAD: Type definitions, function signatures, API docs (read source/docs instead)
1811
+
1812
+ ### NEVER store in core memory
1813
+ - API documentation (read source/docs when needed)
1814
+ - Type definitions from libraries (import them)
1815
+ - Function signatures (read source code)
1816
+ - Completed goals (clear them immediately)
1817
+
1818
+ ### Update core memory immediately when
1819
+ - Starting new task: clear old goal, set new specific goal
1820
+ - Making progress: update progress checklist (keep concise)
1821
+ - Switching files: update context with current working files
1822
+ - Task completed: clear goal/progress, set next task
1823
+ - Approaching char limit: compress or remove outdated info`,
1870
1824
  args: {
1871
1825
  block: tool.schema.enum(["goal", "progress", "context"]).describe(
1872
1826
  "Which memory block to update (goal/progress/context)"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-working-memory",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Advanced four-tier memory architecture for OpenCode with intelligent pressure monitoring and auto-storage governance",
5
5
  "type": "module",
6
6
  "main": "index.ts",