|
| 1 | +--- |
| 2 | +name: opencode-memory |
| 3 | +description: Use when the user asks to recall prior OpenCode work, previous sessions, plans, prompt history, memory, or earlier project context stored on the local machine. |
| 4 | +compatibility: opencode |
| 5 | +--- |
| 6 | + |
| 7 | +# OpenCode Memory Browser |
| 8 | + |
| 9 | +Lightweight, read-only access to your local OpenCode history. No injection, no bloat — just the ability to look things up when it would help. |
| 10 | + |
| 11 | +This skill is specifically about OpenCode data stored on the local machine. It is not for ChatGPT history, Claude cloud history, generic browser history, or external memory products. |
| 12 | + |
| 13 | +All data lives in local SQLite databases and plain files. You query them directly using `sqlite3` via bash. No bundled scripts or external dependencies needed. |
| 14 | + |
| 15 | +## When to Use |
| 16 | + |
| 17 | +### Auto-trigger (agent decides) |
| 18 | + |
| 19 | +- You are resuming work on a project and suspect prior sessions exist. |
| 20 | +- The user references something done previously ("we did this before", "last time", "that plan we made"). |
| 21 | +- A recurring issue suggests checking if it was encountered before. |
| 22 | +- The user asks about the state of plans, past decisions, or previous approaches. |
| 23 | +- You need context that might exist in history but is not in the current session. |
| 24 | + |
| 25 | +### User-triggered (explicit request) |
| 26 | + |
| 27 | +- "Check my history" |
| 28 | +- "What did we do in the last session?" |
| 29 | +- "Show me my plans" |
| 30 | +- "Search for when we discussed X" |
| 31 | +- "What projects have I worked on?" |
| 32 | +- "Look at previous conversations about Y" |
| 33 | + |
| 34 | +### Do NOT use when |
| 35 | + |
| 36 | +- The task is clearly brand new with no relevant history. |
| 37 | +- Fresh repo context (files, git log) is sufficient. |
| 38 | +- The user explicitly says they don't care about prior work. |
| 39 | + |
| 40 | +## Storage Locations |
| 41 | + |
| 42 | +``` |
| 43 | +Databases: ${XDG_DATA_HOME:-$HOME/.local/share}/opencode/opencode*.db |
| 44 | +Plans: ${XDG_DATA_HOME:-$HOME/.local/share}/opencode/plans/*.md |
| 45 | +Session diffs: ${XDG_DATA_HOME:-$HOME/.local/share}/opencode/storage/session_diff/<session-id>.json |
| 46 | +Prompt history: ${XDG_STATE_HOME:-$HOME/.local/state}/opencode/prompt-history.jsonl |
| 47 | +``` |
| 48 | + |
| 49 | +The database path respects `$XDG_DATA_HOME` if set (default: `~/.local/share`). |
| 50 | + |
| 51 | +Important: OpenCode may store session history in multiple channel-specific databases such as: |
| 52 | + |
| 53 | +- `opencode.db` |
| 54 | +- `opencode-dev.db` |
| 55 | +- `opencode-local.db` |
| 56 | +- other `opencode-<channel>.db` files |
| 57 | + |
| 58 | +When recalling prior work, search **all local `opencode*.db` files**, not just `opencode.db`. |
| 59 | + |
| 60 | +## Database Schema (what matters) |
| 61 | + |
| 62 | +- **project** — `id` (text PK), `worktree` (path), `name` (often NULL, derive from worktree basename) |
| 63 | +- **session** — `id` (text, e.g. `ses_xxx`), `project_id` (FK), `parent_id` (NULL = main session, set = subagent), `title`, `summary`, `time_created`, `time_updated` |
| 64 | +- **message** — `id`, `session_id` (FK), `data` (JSON with `$.role` = `"user"` or `"assistant"`), `time_created` |
| 65 | +- **part** — `id`, `message_id` (FK), `session_id` (FK), `data` (JSON with `$.type` = `"text"` and `$.text` = content) |
| 66 | + |
| 67 | +Timestamps are Unix milliseconds. Use `datetime(col/1000, 'unixepoch', 'localtime')` to display them. |
| 68 | + |
| 69 | +## Ready-to-Use Queries |
| 70 | + |
| 71 | +All queries use `sqlite3` in read-only mode. Always run via bash. |
| 72 | + |
| 73 | +**Shorthand used below:** |
| 74 | + |
| 75 | +``` |
| 76 | +DATA_ROOT="${XDG_DATA_HOME:-$HOME/.local/share}/opencode" |
| 77 | +STATE_ROOT="${XDG_STATE_HOME:-$HOME/.local/state}/opencode" |
| 78 | +DBS=("$DATA_ROOT"/opencode*.db) |
| 79 | +``` |
| 80 | + |
| 81 | +If the glob does not match anything, verify the storage root first with `ls "$DATA_ROOT"`. |
| 82 | + |
| 83 | +### Quick summary |
| 84 | + |
| 85 | +```bash |
| 86 | +for DB in "${DBS[@]}"; do |
| 87 | + [ -f "$DB" ] || continue |
| 88 | + DB_URI="file:${DB}?mode=ro" |
| 89 | + printf '\n== %s ==\n' "$DB" |
| 90 | + sqlite3 "$DB_URI" " |
| 91 | + SELECT 'projects', COUNT(*) FROM project |
| 92 | + UNION ALL SELECT 'sessions (main)', COUNT(*) FROM session WHERE parent_id IS NULL |
| 93 | + UNION ALL SELECT 'sessions (total)', COUNT(*) FROM session |
| 94 | + UNION ALL SELECT 'messages', COUNT(*) FROM message |
| 95 | + UNION ALL SELECT 'todos', COUNT(*) FROM todo; |
| 96 | + " |
| 97 | +done |
| 98 | +``` |
| 99 | + |
| 100 | +### List projects |
| 101 | + |
| 102 | +Set `DB_URI` to the database you want to inspect first, for example: |
| 103 | + |
| 104 | +```bash |
| 105 | +DB="$DATA_ROOT/opencode-dev.db" |
| 106 | +DB_URI="file:${DB}?mode=ro" |
| 107 | +``` |
| 108 | + |
| 109 | +```bash |
| 110 | +sqlite3 "$DB_URI" " |
| 111 | + SELECT |
| 112 | + COALESCE(p.name, CASE WHEN p.worktree = '/' THEN '(global)' ELSE REPLACE(p.worktree, RTRIM(p.worktree, REPLACE(p.worktree, '/', '')), '') END) AS name, |
| 113 | + p.worktree, |
| 114 | + (SELECT COUNT(*) FROM session s WHERE s.project_id = p.id AND s.parent_id IS NULL) AS sessions |
| 115 | + FROM project p |
| 116 | + ORDER BY p.time_updated DESC |
| 117 | + LIMIT 10; |
| 118 | +" |
| 119 | +``` |
| 120 | + |
| 121 | +### List recent sessions |
| 122 | + |
| 123 | +```bash |
| 124 | +for DB in "${DBS[@]}"; do |
| 125 | + [ -f "$DB" ] || continue |
| 126 | + DB_URI="file:${DB}?mode=ro" |
| 127 | + sqlite3 "$DB_URI" " |
| 128 | + SELECT |
| 129 | + '${DB}' AS db, |
| 130 | + s.id, |
| 131 | + COALESCE(s.title, 'untitled') AS title, |
| 132 | + COALESCE(p.name, CASE WHEN p.worktree = '/' THEN '(global)' ELSE REPLACE(p.worktree, RTRIM(p.worktree, REPLACE(p.worktree, '/', '')), '') END) AS project, |
| 133 | + datetime(s.time_updated/1000, 'unixepoch', 'localtime') AS updated, |
| 134 | + (SELECT COUNT(*) FROM message m WHERE m.session_id = s.id) AS msgs |
| 135 | + FROM session s |
| 136 | + LEFT JOIN project p ON p.id = s.project_id |
| 137 | + WHERE s.parent_id IS NULL |
| 138 | + ORDER BY s.time_updated DESC |
| 139 | + LIMIT 10; |
| 140 | + " |
| 141 | +done |
| 142 | +``` |
| 143 | + |
| 144 | +### Sessions for a specific project |
| 145 | + |
| 146 | +Set `DB_URI` to the likely matching database, then replace the worktree path with the actual project path: |
| 147 | + |
| 148 | +```bash |
| 149 | +sqlite3 "$DB_URI" " |
| 150 | + SELECT s.id, COALESCE(s.title, 'untitled'), |
| 151 | + datetime(s.time_updated/1000, 'unixepoch', 'localtime') |
| 152 | + FROM session s |
| 153 | + JOIN project p ON p.id = s.project_id |
| 154 | + WHERE p.worktree = '/path/to/project' |
| 155 | + AND s.parent_id IS NULL |
| 156 | + ORDER BY s.time_updated DESC |
| 157 | + LIMIT 10; |
| 158 | +" |
| 159 | +``` |
| 160 | + |
| 161 | +To find the worktree for the current directory: `git rev-parse --show-toplevel` |
| 162 | + |
| 163 | +### Read messages from a session |
| 164 | + |
| 165 | +Replace the session ID: |
| 166 | + |
| 167 | +```bash |
| 168 | +sqlite3 "$DB_URI" " |
| 169 | + SELECT |
| 170 | + json_extract(m.data, '$.role') AS role, |
| 171 | + datetime(m.time_created/1000, 'unixepoch', 'localtime') AS time, |
| 172 | + GROUP_CONCAT(json_extract(p.data, '$.text'), char(10)) AS text |
| 173 | + FROM message m |
| 174 | + LEFT JOIN part p ON p.message_id = m.id |
| 175 | + AND json_extract(p.data, '$.type') = 'text' |
| 176 | + WHERE m.session_id = 'SESSION_ID_HERE' |
| 177 | + GROUP BY m.id |
| 178 | + ORDER BY m.time_created ASC |
| 179 | + LIMIT 50; |
| 180 | +" |
| 181 | +``` |
| 182 | + |
| 183 | +### Search across all conversations |
| 184 | + |
| 185 | +Replace the search term: |
| 186 | + |
| 187 | +```bash |
| 188 | +for DB in "${DBS[@]}"; do |
| 189 | + [ -f "$DB" ] || continue |
| 190 | + DB_URI="file:${DB}?mode=ro" |
| 191 | + sqlite3 "$DB_URI" " |
| 192 | + SELECT |
| 193 | + '${DB}' AS db, |
| 194 | + s.id AS session_id, |
| 195 | + COALESCE(s.title, 'untitled') AS title, |
| 196 | + json_extract(m.data, '$.role') AS role, |
| 197 | + datetime(m.time_created/1000, 'unixepoch', 'localtime') AS time, |
| 198 | + substr(json_extract(p.data, '$.text'), 1, 200) AS snippet |
| 199 | + FROM part p |
| 200 | + JOIN message m ON m.id = p.message_id |
| 201 | + JOIN session s ON s.id = m.session_id |
| 202 | + WHERE s.parent_id IS NULL |
| 203 | + AND json_extract(p.data, '$.type') = 'text' |
| 204 | + AND json_extract(p.data, '$.text') LIKE '%SEARCH_TERM%' |
| 205 | + ORDER BY m.time_created DESC |
| 206 | + LIMIT 10; |
| 207 | + " |
| 208 | +done |
| 209 | +``` |
| 210 | + |
| 211 | +### List saved plans |
| 212 | + |
| 213 | +```bash |
| 214 | +ls -lt "$DATA_ROOT"/plans/*.md 2>/dev/null | head -20 |
| 215 | +``` |
| 216 | + |
| 217 | +To read a specific plan: |
| 218 | + |
| 219 | +```bash |
| 220 | +cat "$DATA_ROOT"/plans/FILENAME.md |
| 221 | +``` |
| 222 | + |
| 223 | +### Show recent prompt history |
| 224 | + |
| 225 | +```bash |
| 226 | +tail -20 "$STATE_ROOT"/prompt-history.jsonl |
| 227 | +``` |
| 228 | + |
| 229 | +Each line is a JSON object. The user's input is typically in the `input` or `text` field. |
| 230 | + |
| 231 | +## Workflow |
| 232 | + |
| 233 | +### Quick recall (most common) |
| 234 | + |
| 235 | +1. Check **prompt history first** with `rg -n -i "term1|term2" "$STATE_ROOT/prompt-history.jsonl"` to recover the user's original wording and likely time window. |
| 236 | +2. Run the **summary** query across all local databases to see which DB/channel has the relevant history. |
| 237 | +3. If you need sessions for the current project, get the worktree with `git rev-parse --show-toplevel`, then run the **project sessions** query against the likely matching DB(s). |
| 238 | +4. If you need a specific topic, run the **search** query across all DBs using both the exact phrase and adjacent terms. |
| 239 | +5. If you need full conversation detail, run the **messages** query with the session ID from the matching DB. |
| 240 | + |
| 241 | +### Plan review |
| 242 | + |
| 243 | +1. List plans with `ls -lt "$DATA_ROOT"/plans/*.md`. |
| 244 | +2. Read a plan with `cat "$DATA_ROOT"/plans/<filename>.md`. |
| 245 | + |
| 246 | +### Deep investigation |
| 247 | + |
| 248 | +1. Search prompt history first to anchor wording/date. |
| 249 | +2. Run **projects/sessions** across all local DBs. |
| 250 | +3. Search with neighboring terms, not just the user’s remembered phrasing. |
| 251 | +4. Read only the best candidate session tails before expanding further. |
| 252 | +5. Cross-reference with session diffs or plans if needed. |
| 253 | + |
| 254 | +## Critical Rules |
| 255 | + |
| 256 | +1. **Read-only.** Never write to or modify the database or any OpenCode files. |
| 257 | +2. **Use bash + sqlite3.** Do not try to read `opencode*.db` with the Read tool — they are binary files. Always query via `sqlite3` in bash. |
| 258 | +3. **Don't dump everything.** Use `LIMIT` and `LIKE` to keep output focused. The database can contain tens of thousands of messages. |
| 259 | +4. **Summarize for the user.** After retrieving data, distill the relevant parts. Don't paste raw query output. |
| 260 | +5. **Respect privacy.** Session history may contain sensitive data. Only surface what is relevant to the current task. |
| 261 | +6. **Set path variables first.** At the start of any memory lookup, set `DATA_ROOT`, `STATE_ROOT`, and `DBS` exactly as shown above so the commands work on XDG and non-XDG setups and cover every local channel database. |
| 262 | + |
| 263 | +## Fallback: Web UI |
| 264 | + |
| 265 | +If the user needs visual dashboards or a browsable interface: |
| 266 | + |
| 267 | +1. Check if OpenCode web is running: `curl -s http://127.0.0.1:4096/api/health 2>/dev/null || echo "not running"` |
| 268 | +2. If running, direct the user to `http://127.0.0.1:4096`. |
| 269 | +3. If not running, suggest `opencode web`. |
| 270 | +4. Note: `opencode.local` only works with mDNS enabled (`opencode web --mdns`). Don't assume it exists. |
| 271 | + |
| 272 | +## Deep Reference |
| 273 | + |
| 274 | +See `references/storage-format.md` for the full storage layout, all table schemas, and additional query examples. |
0 commit comments