DEV Community

Angelo Pantano
Angelo Pantano

Posted on

Giving an OpenCode Coding Agent Persistent, Editable Memory

Giving an OpenCode Coding Agent Persistent, Editable Memory

AI coding agents are good at using the context currently in front of them. They are much less reliable at carrying useful context from one session to the next.

Project conventions, decisions made during a debugging session, and preferences that were obvious yesterday often need to be re-explained after a restart or after context compaction.

I maintain @ghilteras/opencode-agent-memory, an experimental plugin for OpenCode that explores an alternative: treating agent memory as editable, scoped Markdown state on disk rather than as an opaque remote service.

The problem with always-in-context instructions

AGENTS.md and custom instruction files are a good start. I rely on them heavily. But they are a single flat document with no notion of scope, no size enforcement, and no dedicated operations for the agent to maintain it.

What I actually wanted was structured state:

  • some facts belong to me globally, across every project
  • some facts belong only to one codebase
  • each piece should have a description telling the agent how to use it
  • each piece should have a size limit so it cannot silently grow without bound
  • the agent should be able to read and rewrite that state with explicit tools

Memory blocks

The plugin gives the agent three tools: memory_list, memory_set, and memory_replace. Blocks are plain Markdown files with YAML frontmatter.

Global blocks live in ~/.config/opencode/memory/*.md and are shared across projects. Project blocks live in .opencode/memory/*.md and are shared across sessions in that codebase, and are gitignored automatically.

Each block has:

Field Type Default Purpose
label string filename unique identifier
description string generic fallback tells the agent how to use the block
limit integer 5000 maximum characters
read_only boolean false prevents agent edits

The description field carries more weight than it looks like it should. Without it, the agent gets a generic fallback and does not know when the block is relevant. This mirrors the emphasis Letta puts on describing memory blocks well.

Three blocks are seeded on first run: persona and human globally, project for the current codebase.

The journal

Memory blocks are curated state. Some things are not: observations, dead ends, discoveries, decisions, and the reasoning behind them.

For that the plugin adds an optional append-only journal with journal_write, journal_search, and journal_read. Entries are Markdown files with YAML frontmatter stored under ~/.config/opencode/journal/, and each entry records which project, model, provider, agent, and session it came from.

The journal is deliberately opt-in. Enabling it is a line in ~/.config/opencode/agent-memory.json:

{
  "journal": {
    "enabled": true
  }
}
Enter fullscreen mode Exit fullscreen mode

Append-only plus retrieval is what makes it useful. I do not want the agent rewriting history; I want it to be able to find what happened last month.

Local semantic search

journal_search uses local embeddings rather than a hosted API. Entries are embedded with paraphrase-multilingual-MiniLM-L12-v2 (384 dimensions, multilingual) through Transformers.js, and the model is cached locally. Journal content does not leave the machine for search.

Two implementation details were worth the effort:

Embedding files are versioned. Each .embedding sidecar stores { v, model, dimension, vector }. If the stored dimension does not match the current model, the entry degrades to text matching instead of failing the search. Legacy bare-array embeddings from earlier versions remain readable.

The in-memory index is fingerprinted. journal_search keeps an index per store instance and re-reads an entry only when the entry .md or its .embedding sidecar changed (mtime plus size). Regenerating or deleting a sidecar is picked up on the next search without a restart, and the embedding model is warmed up in the background at plugin init so the first search after a restart does not pay the cold model-load cost.

There is also a deliberate retrieval floor: since v0.4.2, a query whose text matches an entry title (in either direction, case-insensitive) is guaranteed a high score, so title-based pointers stay retrievable even for entries with long bodies.

Operational trade-offs

I want to be clear about the failure modes, because memory systems fail quietly.

Stale memory is worse than no memory. A block that says something true six weeks ago will be confidently applied today. The description field and size limits help the agent judge relevance, but they do not make the content true. Having the state as editable Markdown on disk means you can inspect and fix it directly; that is a feature, not a workaround.

Automatic persistence is not the same as truth. The journal records what the agent observed, including wrong conclusions. Treat it as a log, not a knowledge base.

The journal is opt-in because silent collection is worse than explicit collection.

Installation

Requires OpenCode v1.0.115 or later.

{
  "plugin": ["@ghilteras/opencode-agent-memory@0.4.3"]
}
Enter fullscreen mode Exit fullscreen mode

OpenCode fetches unpinned plugins from npm on each startup; pinned versions are cached and need a manual bump. Restart OpenCode after changing plugin configuration; editing the config file alone is not enough to load a new plugin version.

A cacheDir key at the top level of agent-memory.json relocates the Transformers.js model cache if you prefer not to use the default Hugging Face cache location.

What I would like feedback on

  • What belongs in global memory versus project memory in your workflow?
  • How should stale memories be surfaced rather than silently applied?
  • How useful is semantic retrieval over the journal compared with plain text search, in practice?
  • What should survive context compaction, and what should be allowed to decay?

Status

This is experimental software, MIT licensed. It is maintained at https://github.com/Ghilteras/opencode-agent-memory and published as @ghilteras/opencode-agent-memory. It is not built by or affiliated with the OpenCode team.

If you try it, issues and concrete workflow reports are welcome.

Top comments (0)