- Bump version to 1.2.0 - Add package.json exports and files whitelist - Update .gitignore to exclude .opencode/ and .opencode-agenthub/ - Fix docs: active files tracked in tool.execute.after (not before) - Fix docs: exitCode undefined is ignored (not success) - Fix docs: session ID is hashed in storage path - Fix docs: workspace memory survives within same workspace
10 KiB
Architecture Documentation
Overview
The Working Memory Plugin implements a three-layer memory architecture designed to preserve context across OpenCode session compactions.
┌─────────────────────────────────────────────────────────────┐
│ LAYER 1: WORKSPACE MEMORY (Long-term, cross-session) │
│ • Persistent storage: ~/.local/share/opencode-working-... │
│ • Types: feedback | project | decision | reference │
│ • Sources: explicit | compaction | manual │
│ • Limits: 5200 chars / 28 entries │
│ • Survives: session reset, compaction (same workspace) │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ LAYER 2: HOT SESSION STATE (Short-term, per-session) │
│ • Session-scoped tracking: active files, open errors │
│ • Storage: sessions/{sessionID}.json │
│ • Auto-extracted from tool usage patterns │
│ • Cleared: on new session start │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ LAYER 3: NATIVE OPENCODE STATE │
│ • Uses OpenCode's built-in todos during compaction │
│ • No additional storage required │
│ • Delegated to OpenCode's native features │
└─────────────────────────────────────────────────────────────┘
Layer 1: Workspace Memory
Purpose
Long-term memory that persists across sessions within the same workspace. Perfect for:
- Project conventions and patterns
- Important decisions that span sessions
- User preferences for this codebase
Storage
- Location:
~/.local/share/opencode-working-memory/workspaces/{workspaceKey}/workspace-memory.json - Workspace Key: First 16 chars of
sha256(realpath(workspaceRoot)) - Schema:
{ version: 1, workspace: { root: string, key: string }, limits: { maxRenderedChars: 5200, maxEntries: 28 }, entries: LongTermMemoryEntry[], updatedAt: string }
Entry Types
| Type | Purpose | Example |
|---|---|---|
feedback |
User preferences | "User prefers functional React components" |
project |
Project-level info | "This monorepo uses turborepo" |
decision |
Important decisions | "Use PostgreSQL for primary database" |
reference |
Key references | "API endpoints defined in src/api/" |
Entry Sources
| Source | Confidence | How Added |
|---|---|---|
explicit |
1.0 | User said "remember this" |
compaction |
0.75 | Extracted during compaction |
manual |
varies | Programmatically added |
Memory Extraction
During compaction, the plugin scans for <workspace_memory_candidates> blocks:
<workspace_memory_candidates>
- [decision] Use npm cache for plugin loading
- [project] This repo uses TypeScript with strict mode
</workspace_memory_candidates>
Quality Gate: Not all candidates become memories. The plugin rejects:
- Git commit hashes (e.g.,
abc1234) - Raw errors (e.g.,
Error: something failed) - Stack traces
- Path-heavy facts (>50% paths)
- Very short text (<20 chars)
Deduplication
Memories are deduplicated using canonical text matching:
- Normalize: lowercase, strip punctuation, collapse whitespace
- Hash the canonical text
- Keep the entry with highest confidence
System Prompt Injection
Workspace memory is injected at the top of every message:
<workspace_memory>
- [decision] Use npm cache for plugin loading, not npm link
- [project] This repo uses opencode-agenthub plugin system
- [reference] Storage: ~/.local/share/opencode-working-memory/...
</workspace_memory>
Layer 2: Hot Session State
Purpose
Track current session context automatically:
- What files are you working on?
- What errors are currently open?
- What decisions were made recently?
Storage
- Location:
~/.local/share/opencode-working-memory/workspaces/{workspaceKey}/sessions/{hashedSessionID}.json - Schema:
{ version: 1, sessionID: string, turn: number, updatedAt: string, activeFiles: ActiveFile[], openErrors: OpenError[], recentDecisions: SessionDecision[] }
Active Files
Automatically tracked from tool.execute.after events:
| Action | Weight |
|---|---|
edit |
50 |
write |
45 |
grep |
30 |
read |
20 |
Files are ranked by: ACTION_WEIGHT[action] + count * 3
Open Errors
Tracked from tool.execute.after events when exitCode !== 0:
| Category | Trigger Pattern |
|---|---|
typecheck |
TS####: or TypeScript errors |
test |
Test failures |
lint |
ESLint warnings/errors |
build |
Build failures |
runtime |
Error:, TypeError:, etc. |
False Positive Guards:
- Commands like
git log,catwith "error" in output are ignored - Only actual command failures (
exitCode !== 0) trigger errors exitCode === undefinedis ignored (no error created, no error cleared)
Error Fingerprinting
Errors are fingerprinted by:
- Extract error message summary
- Generate fingerprint:
first 12 chars of sha256(summary) - Group similar errors by fingerprint
recentDecisions
Short-term decisions made this session. Candidates for promotion to workspace memory during compaction.
System Prompt Injection
Hot session state is injected after workspace memory:
---
<workspace_memory_candidates>
- [project] This repo uses TypeScript with strict mode
</workspace_memory_candidates>
Active Files:
- src/plugin.ts (edit, 18x)
- tests/plugin.test.ts (edit, 5x)
Open Errors: (none)
Layer 3: Native OpenCode State
Purpose
Delegate task tracking to OpenCode's native features.
Behavior
- Uses OpenCode's built-in
todosduring compaction - No additional storage or injection required
- Allows the agent to manage task lists natively
Plugin Hooks
The plugin hooks into OpenCode lifecycle events:
experimental.chat.system.transform
Injects workspace memory and hot session state into system prompt.
tool.execute.after
- Tracks active files (read, grep, edit, write actions)
- Tracks open errors from failed commands
- Clears errors when commands succeed
- Ignores
exitCode === undefined(successful commands without explicit exit codes)
experimental.session.compacting
Extracts workspace memory candidates from conversation. Applies quality gate, deduplication, and source priority.
event (session.compacted, session.deleted)
session.compacted: Promote session decisions to workspace memorysession.deleted: Clean up session state files
Quality Guarantees
No False Positive Errors
// Bad: Would create false positive
"Error: something failed" in output
// Good: Actually failed
exitCode === 1 && output.includes("Error")
// Good: Actually succeeded
exitCode === 0 (clears errors for that category)
// Good: Ignore ambiguous cases
exitCode === undefined → skip error tracking
Negative Memory Filtering
// Correctly interpreted
"don't remember this" → NOT added to memory
"不要記住這個" → NOT added to memory
"remember this" → added to memory candidates
Canonical Deduplication
// Same memory (after normalization)
"Use npm cache for plugins"
"USE NPM CACHE for plugins!!"
"use npm cache for plugins."
// All map to same canonical key
canonical("Use npm cache for plugins") === "use npm cache for plugins"
Compaction Quality Gate
// Rejected (not valuable as long-term memory)
"4832b38 fix: something" // git hash
"Error: something failed" // raw error
"at Object.method (file.ts:42)" // stack trace
"/Users/x/project/file.ts /Users/x/project/other.ts" // path-heavy
// Accepted
"[decision] Use npm cache for plugin loading" // good pattern
File System Layout
~/.local/share/opencode-working-memory/
└── workspaces/
└── {workspaceKey}/
├── workspace-memory.json # Long-term memory
└── sessions/
└── {hashedSessionID}.json # Session state
Workspace Key
// First 16 chars of SHA-256 hash of workspace root realpath
const workspaceKey = sha256(realpath(workspaceRoot)).slice(0, 16)
Performance Considerations
Memory Budgets
| Layer | Max Chars | Max Entries |
|---|---|---|
| Workspace Memory | 5200 | 28 |
| Hot Session State | 1200 | 8 files, 3 errors |
Injection Overhead
- Workspace memory: ~200-500 chars per message
- Hot session state: ~200-400 chars per message
- Total: ~400-900 chars per message (minimal)
Storage Footprint
- Workspace memory: ~2-5 KB per workspace
- Session state: ~1-3 KB per session
- Auto-cleanup on workspace/session deletion
Extension Points
Custom Memory Types
Add new types in src/types.ts:
export type LongTermType = "feedback" | "project" | "decision" | "reference" | "custom";
Custom Error Categories
Add new categories in src/types.ts:
export type ErrorCategory = "typecheck" | "test" | "lint" | "build" | "runtime" | "custom";
Custom Extraction Patterns
Modify src/extractors.ts to add new extraction patterns.
Migration Notes
Memory V1 to V2
The plugin automatically migrates old format files to the new three-layer architecture. No manual intervention needed.
Last Updated: April 2026
Implementation: src/plugin.ts, src/extractors.ts, src/workspace-memory.ts, src/session-state.ts