mirror of
https://github.com/sdwolf4103/opencode-working-memory.git
synced 2026-07-17 12:56:42 +02:00
606dcfac12
- Fix outdated hook names: prompt:before → experimental.chat.system.transform - Fix outdated hook names: tool.execute.before → (removed, file tracking is in tool.execute.after) - Fix outdated hook names: compaction:before → experimental.session.compacting - Add missing event hook documentation - Fix README example to show correct <hot_session_state> tag instead of <workspace_memory_candidates>
351 lines
10 KiB
Markdown
351 lines
10 KiB
Markdown
# 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, workspace switch │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
↓
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ 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**:
|
|
```typescript
|
|
{
|
|
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**:
|
|
1. Normalize: lowercase, strip punctuation, collapse whitespace
|
|
2. Hash the canonical text
|
|
3. 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**:
|
|
```typescript
|
|
{
|
|
version: 1,
|
|
sessionID: string,
|
|
turn: number,
|
|
updatedAt: string,
|
|
activeFiles: ActiveFile[],
|
|
openErrors: OpenError[],
|
|
recentDecisions: SessionDecision[]
|
|
}
|
|
```
|
|
|
|
### Active Files
|
|
|
|
Automatically tracked from `tool.execute.before` events:
|
|
|
|
| Action | Ranking Boost |
|
|
|--------|---------------|
|
|
| `write` | 4x |
|
|
| `edit` | 3x |
|
|
| `read` | 2x |
|
|
| `grep` | 1x |
|
|
|
|
Files are ranked by: `count * action_weight * recency_decay`
|
|
|
|
### 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`, `cat` with "error" in output are ignored
|
|
- Only actual command failures (`exitCode !== 0`) trigger errors
|
|
- `exitCode === undefined` is treated as success (no error tracking)
|
|
|
|
### Error Fingerprinting
|
|
|
|
Errors are fingerprinted by:
|
|
1. Extract error message summary
|
|
2. Generate fingerprint: `first 12 chars of sha256(summary)`
|
|
3. 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 `todos` during 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 memory
|
|
- `session.deleted`: Clean up session state files
|
|
|
|
## Quality Guarantees
|
|
|
|
### No False Positive Errors
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// Correctly interpreted
|
|
"don't remember this" → NOT added to memory
|
|
"不要記住這個" → NOT added to memory
|
|
"remember this" → added to memory candidates
|
|
```
|
|
|
|
### Canonical Deduplication
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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`:
|
|
```typescript
|
|
export type LongTermType = "feedback" | "project" | "decision" | "reference" | "custom";
|
|
```
|
|
|
|
### Custom Error Categories
|
|
|
|
Add new categories in `src/types.ts`:
|
|
```typescript
|
|
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` |