OpenCode Working Memory provides a three-layer memory architecture for AI agents:
- Workspace Memory - Long-term memory that persists across sessions (decisions, project info, references)
- Hot Session State - Automatic tracking of active files, open errors, and recent decisions
- Native OpenCode State - Delegated to OpenCode's built-in todos during compaction
Written in TypeScript for the OpenCode agent environment.
# For development
git clone https://github.com/sdwolf4103/opencode-working-memory.git
cd opencode-working-memory
npm install
npm test
npm run typecheck
# For usage (see README.md)# TypeScript strict mode - fix all type errors before committing
npx tsc --noEmit# Run all tests
npm test
# Run specific test file
npx node --test --experimental-strip-types tests/plugin.test.tsTests verify:
- Error extraction false positive guards
- Explicit memory trigger patterns
- Negative memory request filtering
- Compaction candidate quality gate
- Workspace memory rendering
- Session state tracking
opencode-working-memory/
├── index.ts # Plugin entry point (exports PluginModule)
├── src/
│ ├── plugin.ts # Main plugin implementation
│ ├── extractors.ts # Memory extraction logic
│ ├── workspace-memory.ts # Workspace memory management
│ ├── session-state.ts # Session state tracking
│ ├── storage.ts # File storage utilities
│ ├── paths.ts # Path utilities
│ ├── opencode.ts # OpenCode SDK types
│ └── types.ts # Type definitions
├── tests/
│ ├── plugin.test.ts # Plugin hook tests
│ ├── extractors.test.ts # Extractor tests
│ └── workspace-memory.test.ts # Workspace memory tests
├── package.json # Plugin manifest
├── tsconfig.json # TypeScript config
├── LICENSE # MIT license
├── README.md # User documentation
├── AGENTS.md # This file (developer guide)
└── docs/ # Detailed documentation
├── installation.md
├── architecture.md
└── configuration.md
// ✅ REQUIRED: Full type annotations, no implicit any
async function loadWorkspaceMemory(
workspaceKey: string
): Promise<WorkspaceMemoryStore | null>
// ❌ AVOID: Implicit any types
async function loadWorkspaceMemory(workspaceKey) { }// ✅ REQUIRED: Define types at module top
export type LongTermMemoryEntry = {
id: string;
type: LongTermType;
text: string;
source: LongTermSource;
confidence: number;
status: "active" | "superseded";
createdAt: string;
updatedAt: string;
};
// ✅ USE: Union types for variants (not enums)
export type LongTermType = "feedback" | "project" | "decision" | "reference";
export type LongTermSource = "explicit" | "compaction" | "manual";
// ✅ USE: const assertions for limits
export const LONG_TERM_LIMITS = {
maxRenderedChars: 3600,
maxEntries: 28,
} as const;// ✅ REQUIRED: Group and order imports
// 1. Node.js built-ins
import { existsSync } from "fs";
import { mkdir, readFile, writeFile } from "fs/promises";
import { join } from "path";
// 2. Third-party (OpenCode SDK)
import type { Plugin } from "@opencode-ai/plugin";
// 3. Local modules
import { loadWorkspaceMemory } from "./storage.js";// ✅ REQUIRED: camelCase for variables & functions
const maxEntries = 28;
async function loadWorkspaceMemory() { }
// ✅ REQUIRED: SCREAMING_SNAKE_CASE for constants
const LONG_TERM_LIMITS = { maxRenderedChars: 3600, maxEntries: 28 };
const HOT_STATE_LIMITS = { maxRenderedChars: 700 };
// ✅ REQUIRED: PascalCase for types
type WorkspaceMemoryStore = { ... };
type SessionState = { ... };
// ✅ REQUIRED: get*/load*/save* naming for file operations
function getWorkspaceMemoryPath(workspaceKey: string): string { }
async function loadWorkspaceMemory(workspaceKey: string): Promise<WorkspaceMemoryStore | null> { }
async function saveWorkspaceMemory(memory: WorkspaceMemoryStore): Promise<void> { }// ✅ REQUIRED: Parameters on separate lines if > 80 chars
async function extractWorkspaceMemoryCandidates(
content: string
): Promise<WorkspaceMemoryCandidate[]> {
// ...
}
// ✅ REQUIRED: Explicit return types (no inference)
function renderWorkspaceMemory(
entries: LongTermMemoryEntry[],
maxChars: number
): string {
// ...
}
// ✅ REQUIRED: Async for file/network I/O
async function saveWorkspaceMemory(memory: WorkspaceMemoryStore): Promise<void> {
// ...
}// ✅ REQUIRED: Try-catch with graceful degradation
async function loadWorkspaceMemory(workspaceKey: string): Promise<WorkspaceMemoryStore | null> {
const path = getWorkspaceMemoryPath(workspaceKey);
if (!existsSync(path)) return null;
try {
const content = await readFile(path, "utf-8");
return JSON.parse(content) as WorkspaceMemoryStore;
} catch (error) {
// Plugin should never block agent - return null and continue
return null;
}
}
// ✅ REQUIRED: Validate JSON before use
const data = JSON.parse(content);
const typedData = data as WorkspaceMemoryStore; // Explicit cast after validation// ✅ REQUIRED: Section headers for major sections
// ============================================================================
// Workspace Memory: Long-term cross-session storage
// ============================================================================
// ✅ REQUIRED: Block comments for complex logic
// Quality gate: Reject candidates that are git hashes, errors, or path-heavy
function shouldAcceptWorkspaceMemoryCandidate(candidate: string): boolean {
// ...
}
// ✅ USE: Inline comments sparingly for non-obvious logic
const canonical = normalizeText(text); // Lowercase, strip punctuation, collapse whitespace// index.ts
import { MemoryV2Plugin } from "./src/plugin.ts";
export default {
id: "working-memory",
server: MemoryV2Plugin,
};- Location:
~/.local/share/opencode-working-memory/workspaces/{workspaceKey}/workspace-memory.json - Workspace Key: First 16 chars of
sha256(realpath(workspaceRoot)) - Schema: See
src/types.ts:WorkspaceMemoryStore - Limits: 3600 chars, 28 entries max
- Location:
~/.local/share/opencode-working-memory/workspaces/{workspaceKey}/sessions/{sessionID}.json - Session ID: Hash of session ID from OpenCode
- Schema: See
src/types.ts:SessionState
| Type | Purpose | Stale After |
|---|---|---|
feedback |
User preferences | 90 days |
project |
Project info | 60 days |
decision |
Important decisions | 45 days |
reference |
Key references | 90 days |
- False Positive Error Prevention: Commands with "error" in output but
exitCode === undefinedare not tracked as errors - Negative Memory Filtering: "don't remember" patterns are correctly interpreted
- Compaction Quality Gate: Rejects git hashes, stack traces, path-heavy facts
Injects workspace memory and hot session state into system prompt.
- Tracks active files (read, grep, edit, write actions)
- Tracks open errors from failed commands
- Clears errors when commands succeed
- Ignores
exitCode === undefined
Extracts workspace memory candidates from conversation, applies quality gate and deduplication.
session.compacted: Promote session decisions to workspace memorysession.deleted: Clean up session state files
- Workspace Memory: Check
~/.local/share/opencode-working-memory/workspaces/*/workspace-memory.jsonafter compaction - Session State: Check
~/.local/share/opencode-working-memory/workspaces/*/sessions/*.jsonafter tool usage - Error Tracking: Run failing commands, verify errors appear in session state
- Error Clearing: Run successful commands, verify errors are cleared
- File not found: Ensure
~/.local/share/opencode-working-memory/exists and is writable - Type errors: Check all imports use
import type { ... }for types - Memory not persisting: Verify workspace key is consistent (same workspace = same key)
- False positive errors: Check
exitCodehandling inplugin.ts
- Workspace memory budget: 3600 chars injected into system prompt
- Session state budget: 700 chars injected into system prompt
- Total overhead: typically well below configured maximums
- Storage footprint: ~2-5 KB per workspace for memory, ~1-3 KB per session
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Make changes following the code style guidelines above
- Run tests:
npm test && npm run typecheck - Commit with descriptive message:
git commit -m "feat: add ..." - Push to your fork:
git push origin feature/my-feature - Open a pull request
See docs/architecture.md for detailed technical documentation including:
- Three-layer memory architecture
- Memory extraction and quality gates
- Error fingerprinting
- Deduplication strategies
Last Updated: April 2026
Plugin Status: Production (Memory V2 architecture)