Skip to content

Latest commit

 

History

History
134 lines (102 loc) · 7.75 KB

AGENTS.md

File metadata and controls

134 lines (102 loc) · 7.75 KB

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

The optional native knowledge-base integration test needs a cached embedding model: OPENCODE_MEMORY_TEST_RAG_INTEGRATION=1 bun test test/knowledge.test.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
    knowledge-config.ts # named knowledge bases from the user's config.toml
    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
    knowledge.ts        # read-only search / read / list over named rag.toml scopes
    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 memory_, knowledge_, and session_ prefixes. Knowledge bases are read-only and configured separately from the memory corpus.
  • config — calls applyConfig() 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 prompts
  • tool.execute.after — guard.toolAfter, tracks memory/session tool usage, fires nudges
  • experimental.session.compacting — guard.compacting, injects preserve-through-compaction context

Separation of concerns

Every tool has two layers:

  1. runXxx(input) — pure TypeScript function, no tool() wrapper. Takes plain arguments, reads env lazily, returns a string. Covered directly by integration tests.
  2. defineTool({ description, input, execute }) — a v2-oriented definition that calls runXxx and 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() with zod/v4 input 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. SessionState in hooks/guard.ts). Only hoist to a top-level types.ts when 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.ts provides withMemoryDir(cb) which creates a fresh temp dir, points $OPENCODE_MEMORY_DIR at 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.ts and session.test.ts are integration tests. session.test.ts builds a temp SQLite DB with the minimum schema we actually touch; memory.test.ts searches 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

  1. Add a file (or new exports) under src/tools/.
  2. Write the pure runXxx(input) function first. It should be callable from a test with plain arguments and return a string.
  3. 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.
  4. Register it in src/v2.ts and adapt it in src/index.ts under the appropriate namespace (memory_xxx or session_xxx).
  5. Write tests for both the pure helpers and the integration path.
  6. If the tool needs a new permission or a new agent prompt, update src/config.ts and test/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