Imported from mathew-cf/opencode-memory (
AGENTS.md). Install upstream withnpx skills add mathew-cf/opencode-memory. Copyright stays with the author.
AGENTS.md — opencode-memory
Conventions for agents working on the @mathew-cf/opencode-memory codebase.
Quick Reference
| Command | Purpose |
|---|---|
bun test |
Run all tests (bun:test) |
bun run typecheck |
TypeScript check (tsc --noEmit) |
bun run build |
Bundle to dist/ + emit .d.ts |
Run all three before committing:
bun run typecheck && bun test && bun run build
Directory Structure
src/
index.ts # OpenCode v1 adapter — wires tools, hooks, config
v2.ts # OpenCode v2 plugin entry
cli.ts # `opencode-memory` bin: init / status / help
config.ts # applyConfig() — skills.paths, agent prompts, permissions
constants.ts # CATEGORIES, DEFAULT_MEMORY_SUBDIR, STOP_WORDS
lib/
paths.ts # resolveHome, resolveMemoryDir, normPath, ragIndexDir
frontmatter.ts # parseFrontmatter, bumpAccessFields, todayISO
search-terms.ts # parseSearchTerms, countTermMatches, scoreCandidate
rag.ts # ensureRag, ragSearch, spawnRagIndex, downloadModel
db.ts # resolveDbPath, sqlStr, querySqlite
tool-definition.ts # v2-oriented tool definition helper
tools/
memory.ts # search / list / save / access / setup
session.ts # search / read / list (reads opencode.db)
hooks/
guard.ts # tool-call tracking, nudges, compaction context
test/
helpers.ts # withMemoryDir, writeMemoryFile, makeTempDir
frontmatter.test.ts # YAML parser + access-field mutation
search-terms.test.ts # Tokenization + scoring
paths.test.ts # Cross-platform home + memory dir resolution
memory.test.ts # Integration tests against temp memory dirs
session.test.ts # Integration tests against a temp SQLite db
guard.test.ts # Hook state machine + compaction output
config.test.ts # applyConfig() additive behaviour
cli.test.ts # CLI dispatcher + skill-symlink install
skills/
opencode-memory/
SKILL.md # The bundled skill, auto-registered at plugin load
scripts/
sync-version.ts # Sync package.json version into README.md
Architecture
This package ships three surfaces against the same shared core:
| Surface | Entry point | Audience |
|---|---|---|
| OpenCode v1 | src/index.ts |
V1 adapter for tools, hooks, and config |
| OpenCode v2 | src/v2.ts |
Native tool, hook, and skill registration |
opencode-memory |
src/cli.ts |
Humans (init, status) + post-install |
Both plugins share src/tools/memory.ts, src/tools/session.ts, and src/lib/*. Tool definitions use the v2 input and result shape; index.ts adapts them for v1.
OpenCode plugin entry (src/index.ts)
Exports a default v1 plugin object. Its server function returns:
tool— custom tools keyed with thememory_/session_prefixes so names match what skills and prompts already referenceconfig— callsapplyConfig()to register the bundled skill directory, add edit/external_directory permissions for~/opencode-memory/**, and prepend the memory-awareness appendix to the built-in subagent promptstool.execute.after—guard.toolAfter, tracks memory/session tool usage, fires nudgesexperimental.session.compacting—guard.compacting, injects preserve-through-compaction context
Separation of concerns
Every tool has two layers:
runXxx(input)— pure TypeScript function, notool()wrapper. Takes plain arguments, reads env lazily, returns a string. Covered directly by integration tests.defineTool({ description, input, execute })— a v2-oriented definition that callsrunXxxand returns{ content }. The v1 entry converts its input shape and result.
This lets tests cover the real behaviour without constructing a fake OpenCode context.
Env-driven configuration
The lib layer never reads config statically. Both resolveMemoryDir() and resolveDbPath() read process.env fresh on each call. That's how tests inject temp directories (withMemoryDir sets OPENCODE_MEMORY_DIR) and temp databases (session.test.ts sets OPENCODE_DB in beforeAll).
Graceful degradation around rag
The package supplies the native rag CLI for both live keyword search and semantic search. If the binary is unavailable, search returns an empty result rather than throwing to the tool caller; memory_setup reports the installation problem. Pi session search falls back to scanning its session files when keyword prefiltering fails.
Coding Conventions
- Runtime: Bun. No Node-only APIs in src or test.
- Imports:
node:prefix for Node builtins (node:path,node:fs/promises,node:os). - Tool definitions: use
defineTool()withzod/v4input schemas. Keep descriptions actionable — they're the LLM's only spec. Only the v1 adapter imports@opencode-ai/plugin. - Error handling: tool execute paths catch exceptions and return strings. Never throw to the caller; the agent reads whatever you return.
- Types: keep shared types in the file that owns the logic (e.g.
SessionStateinhooks/guard.ts). Only hoist to a top-leveltypes.tswhen two unrelated modules need the same shape. - Pure helpers live in
src/lib/. If a helper uses the filesystem or the shell, it belongs in the tool that calls it. - No organization- or environment-specific references. This is a public plugin — examples should be generic (no internal hostnames, team names, proprietary tools, or ticket IDs).
Test Patterns
test/helpers.tsprovideswithMemoryDir(cb)which creates a fresh temp dir, points$OPENCODE_MEMORY_DIRat it, runs the callback, then cleans up and restores the env.- Pure-logic tests (
frontmatter.test.ts,search-terms.test.ts,paths.test.ts,guard.test.ts,config.test.ts) take <100ms in aggregate — they don't hit the filesystem at all. memory.test.tsandsession.test.tsare integration tests.session.test.tsbuilds a temp SQLite DB with the minimum schema we actually touch;memory.test.tssearches real files through the native rag keyword command.- Ranking assertions are relative, not absolute — e.g.
expect(aIdx).toBeLessThan(bIdx). Pinning exact scores makes the ranker impossible to tune.
Adding a New Tool
- Add a file (or new exports) under
src/tools/. - Write the pure
runXxx(input)function first. It should be callable from a test with plain arguments and return a string. - Wrap it with
defineTool({ description, input, execute }). Keep the description opinionated — tell the agent when to call it and what to look for in the output. - Register it in
src/v2.tsand adapt it insrc/index.tsunder the appropriate namespace (memory_xxxorsession_xxx). - Write tests for both the pure helpers and the integration path.
- If the tool needs a new permission or a new agent prompt, update
src/config.tsandtest/config.test.ts.
Publishing
Versions are synced into README.md automatically via scripts/sync-version.ts:
npm version patch # or minor / major — triggers the version npm script
git push --follow-tags
npm publish --access public